From 52c81cf3b4f03bb30766beeac89f664054367f29 Mon Sep 17 00:00:00 2001 From: Thomasr Date: Tue, 19 Nov 2024 19:34:29 -0500 Subject: [PATCH 01/36] Increase Heap Size --- deploy/docker/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile index 2c2bc5c27..5ecbbd579 100644 --- a/deploy/docker/Dockerfile +++ b/deploy/docker/Dockerfile @@ -17,6 +17,7 @@ COPY server/api-service/lowcoder-server/src/main/resources/application.yaml /low # Add bootstrapfile COPY deploy/docker/api-service/entrypoint.sh /lowcoder/api-service/entrypoint.sh COPY deploy/docker/api-service/init.sh /lowcoder/api-service/init.sh +ENV JAVA_OPTS="-Xmx2G -Xms512M" RUN chmod +x /lowcoder/api-service/*.sh ## From 24ba4bdabef1ee01f968455a42cbc1ccd8625254 Mon Sep 17 00:00:00 2001 From: Thomasr Date: Wed, 20 Nov 2024 03:34:25 -0500 Subject: [PATCH 02/36] convert snapshot migration to use aggregation pipeline --- .../runner/migrations/DatabaseChangelog.java | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java index ddf0422ab..2cd26381a 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java @@ -44,6 +44,7 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; +import java.util.Arrays; import java.util.List; import java.util.Set; @@ -319,35 +320,43 @@ public void addTimeSeriesSnapshotHistory(MongockTemplate mongoTemplate, CommonCo // Create the time-series collection if it doesn't exist if (!mongoTemplate.collectionExists(ApplicationHistorySnapshotTS.class)) { - if(mongoVersion < 5) { + if (mongoVersion < 5) { mongoTemplate.createCollection(ApplicationHistorySnapshotTS.class); } else { mongoTemplate.createCollection(ApplicationHistorySnapshotTS.class, CollectionOptions.empty().timeSeries("createdAt")); } } + Instant thresholdDate = Instant.now().minus(commonConfig.getQuery().getAppSnapshotKeepDuration(), ChronoUnit.DAYS); - List snapshots = mongoTemplate.find(new Query().addCriteria(Criteria.where("createdAt").gte(thresholdDate)), ApplicationHistorySnapshot.class); - snapshots.forEach(snapshot -> { - ApplicationHistorySnapshotTS applicationHistorySnapshotTS = new ApplicationHistorySnapshotTS(); - applicationHistorySnapshotTS.setApplicationId(snapshot.getApplicationId()); - applicationHistorySnapshotTS.setDsl(snapshot.getDsl()); - applicationHistorySnapshotTS.setContext(snapshot.getContext()); - applicationHistorySnapshotTS.setCreatedAt(snapshot.getCreatedAt()); - applicationHistorySnapshotTS.setCreatedBy(snapshot.getCreatedBy()); - applicationHistorySnapshotTS.setModifiedBy(snapshot.getModifiedBy()); - applicationHistorySnapshotTS.setUpdatedAt(snapshot.getUpdatedAt()); - applicationHistorySnapshotTS.setId(snapshot.getId()); - mongoTemplate.insert(applicationHistorySnapshotTS); - mongoTemplate.remove(snapshot); - }); - // Ensure indexes if needed + // Use aggregation to move and transform data + Document match = new Document("$match", + new Document("createdAt", new Document("$gte", thresholdDate))); + + Document project = new Document("$project", new Document() + .append("applicationId", 1) + .append("dsl", 1) + .append("context", 1) + .append("createdAt", 1) + .append("createdBy", 1) + .append("modifiedBy", 1) + .append("updatedAt", 1) + .append("id", "$_id")); // Map MongoDB's default `_id` to `id` if needed. + + Document out = new Document("$out", "applicationHistorySnapshotTS"); // Target collection name + + // Execute the aggregation pipeline + mongoTemplate.getDb() + .getCollection("applicationHistorySnapshot") // Original collection name + .aggregate(Arrays.asList(match, project, out)) + .toCollection(); + ensureIndexes(mongoTemplate, ApplicationHistorySnapshotTS.class, makeIndex("applicationId"), - makeIndex("createdAt") - ); + makeIndex("createdAt")); } + private void addGidField(MongockTemplate mongoTemplate, String collectionName) { // Create a query to match all documents Query query = new Query(); From 0c5c344ea72ce57d26edd093ed468dc8b45c1cd5 Mon Sep 17 00:00:00 2001 From: Thomasr Date: Wed, 20 Nov 2024 03:48:52 -0500 Subject: [PATCH 03/36] delete after copying records --- .../org/lowcoder/runner/migrations/DatabaseChangelog.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java index 2cd26381a..ba5324923 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java @@ -4,6 +4,7 @@ import com.github.cloudyrock.mongock.ChangeSet; import com.github.cloudyrock.mongock.driver.mongodb.springdata.v4.decorator.impl.MongockTemplate; import com.github.f4b6a3.uuid.UuidCreator; +import com.mongodb.client.result.DeleteResult; import lombok.extern.slf4j.Slf4j; import org.bson.Document; import org.lowcoder.domain.application.model.Application; @@ -351,6 +352,10 @@ public void addTimeSeriesSnapshotHistory(MongockTemplate mongoTemplate, CommonCo .aggregate(Arrays.asList(match, project, out)) .toCollection(); + // Delete the migrated records + Query deleteQuery = new Query(Criteria.where("createdAt").gte(thresholdDate)); + DeleteResult deleteResult = mongoTemplate.remove(deleteQuery, ApplicationHistorySnapshot.class); + ensureIndexes(mongoTemplate, ApplicationHistorySnapshotTS.class, makeIndex("applicationId"), makeIndex("createdAt")); From 1eca3afffdb63da4ca7b5dca9d673c0a8fd62853 Mon Sep 17 00:00:00 2001 From: Thomasr Date: Wed, 20 Nov 2024 03:58:20 -0500 Subject: [PATCH 04/36] different migration based on mongo version. --- .../runner/migrations/DatabaseChangelog.java | 101 ++++++++++++------ 1 file changed, 68 insertions(+), 33 deletions(-) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java index ba5324923..153fd765f 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java @@ -4,6 +4,8 @@ import com.github.cloudyrock.mongock.ChangeSet; import com.github.cloudyrock.mongock.driver.mongodb.springdata.v4.decorator.impl.MongockTemplate; import com.github.f4b6a3.uuid.UuidCreator; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; import com.mongodb.client.result.DeleteResult; import lombok.extern.slf4j.Slf4j; import org.bson.Document; @@ -315,47 +317,80 @@ private int getMongoDBVersion(MongockTemplate mongoTemplate) { @ChangeSet(order = "026", id = "add-time-series-snapshot-history", author = "") public void addTimeSeriesSnapshotHistory(MongockTemplate mongoTemplate, CommonConfig commonConfig) { int mongoVersion = getMongoDBVersion(mongoTemplate); - if (mongoVersion < 5) { - log.warn("MongoDB version is below 5. Time-series collections are not supported. Upgrade the MongoDB version."); - } - - // Create the time-series collection if it doesn't exist - if (!mongoTemplate.collectionExists(ApplicationHistorySnapshotTS.class)) { - if (mongoVersion < 5) { - mongoTemplate.createCollection(ApplicationHistorySnapshotTS.class); - } else { - mongoTemplate.createCollection(ApplicationHistorySnapshotTS.class, CollectionOptions.empty().timeSeries("createdAt")); - } - } Instant thresholdDate = Instant.now().minus(commonConfig.getQuery().getAppSnapshotKeepDuration(), ChronoUnit.DAYS); - // Use aggregation to move and transform data - Document match = new Document("$match", - new Document("createdAt", new Document("$gte", thresholdDate))); + if (mongoVersion >= 5) { + // MongoDB version >= 5: Use manual insert query + if (!mongoTemplate.collectionExists(ApplicationHistorySnapshotTS.class)) { + mongoTemplate.createCollection(ApplicationHistorySnapshotTS.class, + CollectionOptions.empty().timeSeries("createdAt")); + } - Document project = new Document("$project", new Document() - .append("applicationId", 1) - .append("dsl", 1) - .append("context", 1) - .append("createdAt", 1) - .append("createdBy", 1) - .append("modifiedBy", 1) - .append("updatedAt", 1) - .append("id", "$_id")); // Map MongoDB's default `_id` to `id` if needed. + // Aggregation pipeline to fetch the records + List aggregationPipeline = Arrays.asList( + new Document("$match", new Document("createdAt", new Document("$gte", thresholdDate))), + new Document("$project", new Document() + .append("applicationId", 1) + .append("dsl", 1) + .append("context", 1) + .append("createdAt", 1) + .append("createdBy", 1) + .append("modifiedBy", 1) + .append("updatedAt", 1) + .append("id", "$_id")) // Map `_id` to `id` if needed + ); + + MongoCollection sourceCollection = mongoTemplate.getDb().getCollection("applicationHistorySnapshot"); + MongoCollection targetCollection = mongoTemplate.getDb().getCollection("applicationHistorySnapshotTS"); + + // Fetch results and insert them into the time-series collection + try (MongoCursor cursor = sourceCollection.aggregate(aggregationPipeline).iterator()) { + while (cursor.hasNext()) { + Document document = cursor.next(); + targetCollection.insertOne(document); // Insert into the time-series collection + } + } - Document out = new Document("$out", "applicationHistorySnapshotTS"); // Target collection name + // Delete the migrated records + Query deleteQuery = new Query(Criteria.where("createdAt").gte(thresholdDate)); + DeleteResult deleteResult = mongoTemplate.remove(deleteQuery, ApplicationHistorySnapshot.class); - // Execute the aggregation pipeline - mongoTemplate.getDb() - .getCollection("applicationHistorySnapshot") // Original collection name - .aggregate(Arrays.asList(match, project, out)) - .toCollection(); + log.info("Deleted {} records from the source collection.", deleteResult.getDeletedCount()); + } else { + // MongoDB version < 5: Use aggregation with $out + if (!mongoTemplate.collectionExists(ApplicationHistorySnapshotTS.class)) { + mongoTemplate.createCollection(ApplicationHistorySnapshotTS.class); // Create a regular collection + } - // Delete the migrated records - Query deleteQuery = new Query(Criteria.where("createdAt").gte(thresholdDate)); - DeleteResult deleteResult = mongoTemplate.remove(deleteQuery, ApplicationHistorySnapshot.class); + // Aggregation pipeline with $out + List aggregationPipeline = Arrays.asList( + new Document("$match", new Document("createdAt", new Document("$gte", thresholdDate))), + new Document("$project", new Document() + .append("applicationId", 1) + .append("dsl", 1) + .append("context", 1) + .append("createdAt", 1) + .append("createdBy", 1) + .append("modifiedBy", 1) + .append("updatedAt", 1) + .append("id", "$_id")), // Map `_id` to `id` if needed + new Document("$out", "applicationHistorySnapshotTS") // Write directly to the target collection + ); + + mongoTemplate.getDb() + .getCollection("applicationHistorySnapshot") + .aggregate(aggregationPipeline) + .toCollection(); + + // Delete the migrated records + Query deleteQuery = new Query(Criteria.where("createdAt").gte(thresholdDate)); + DeleteResult deleteResult = mongoTemplate.remove(deleteQuery, ApplicationHistorySnapshot.class); + + log.info("Deleted {} records from the source collection.", deleteResult.getDeletedCount()); + } + // Ensure indexes on the new collection ensureIndexes(mongoTemplate, ApplicationHistorySnapshotTS.class, makeIndex("applicationId"), makeIndex("createdAt")); From b60590787bb2b8457caecd2a5bbaee2b49191552 Mon Sep 17 00:00:00 2001 From: Thomasr Date: Thu, 21 Nov 2024 08:42:52 -0500 Subject: [PATCH 05/36] modify to use aggregate --- .../runner/task/ArchiveSnapshotTask.java | 138 +++++++++++++++--- 1 file changed, 119 insertions(+), 19 deletions(-) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/task/ArchiveSnapshotTask.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/task/ArchiveSnapshotTask.java index 2fa516379..1a2559ad9 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/task/ArchiveSnapshotTask.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/task/ArchiveSnapshotTask.java @@ -2,12 +2,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.lowcoder.domain.application.model.ApplicationHistorySnapshot; -import org.lowcoder.domain.application.model.ApplicationHistorySnapshotTS; import org.lowcoder.sdk.config.CommonConfig; import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -16,6 +12,11 @@ import java.util.List; import java.util.concurrent.TimeUnit; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.model.Filters; +import org.bson.Document; + @Slf4j @RequiredArgsConstructor @Component @@ -24,23 +25,122 @@ public class ArchiveSnapshotTask { private final CommonConfig commonConfig; private final MongoTemplate mongoTemplate; - @Scheduled(initialDelay = 1, fixedRate = 1, timeUnit = TimeUnit.DAYS) + @Scheduled(initialDelay = 0, fixedRate = 1, timeUnit = TimeUnit.DAYS) public void archive() { + int mongoVersion = getMongoDBVersion(); Instant thresholdDate = Instant.now().minus(commonConfig.getQuery().getAppSnapshotKeepDuration(), ChronoUnit.DAYS); - List snapshots = mongoTemplate.find(new Query().addCriteria(Criteria.where("createdAt").lte(thresholdDate)), ApplicationHistorySnapshotTS.class); - snapshots.forEach(snapshot -> { - ApplicationHistorySnapshot applicationHistorySnapshot = new ApplicationHistorySnapshot(); - applicationHistorySnapshot.setApplicationId(snapshot.getApplicationId()); - applicationHistorySnapshot.setDsl(snapshot.getDsl()); - applicationHistorySnapshot.setContext(snapshot.getContext()); - applicationHistorySnapshot.setCreatedAt(snapshot.getCreatedAt()); - applicationHistorySnapshot.setCreatedBy(snapshot.getCreatedBy()); - applicationHistorySnapshot.setModifiedBy(snapshot.getModifiedBy()); - applicationHistorySnapshot.setUpdatedAt(snapshot.getUpdatedAt()); - applicationHistorySnapshot.setId(snapshot.getId()); - mongoTemplate.insert(applicationHistorySnapshot); - mongoTemplate.remove(snapshot); - }); + + if (mongoVersion >= 5) { + archiveForVersion5AndAbove(thresholdDate); + } else { + archiveForVersionBelow5(thresholdDate); + } + } + + private int getMongoDBVersion() { + Document buildInfo = mongoTemplate.getDb().runCommand(new Document("buildInfo", 1)); + String version = buildInfo.getString("version"); + return Integer.parseInt(version.split("\\.")[0]); // Parse major version } + private void archiveForVersion5AndAbove(Instant thresholdDate) { + log.info("Running archival for MongoDB version >= 5"); + + MongoCollection sourceCollection = mongoTemplate.getDb().getCollection("applicationHistorySnapshotTS"); + MongoCollection targetCollection = mongoTemplate.getDb().getCollection("applicationHistorySnapshot"); + + long totalDocuments = sourceCollection.countDocuments(Filters.lte("createdAt", thresholdDate)); + log.info("Total documents to archive: {}", totalDocuments); + + long processedCount = 0; + + try (MongoCursor cursor = sourceCollection.find(Filters.lte("createdAt", thresholdDate)).iterator()) { + while (cursor.hasNext()) { + Document document = cursor.next(); + + // Transform the document for the target collection + document.put("id", document.getObjectId("_id")); // Map `_id` to `id` + document.remove("_id"); + + // Insert the document into the target collection + try { + targetCollection.insertOne(document); + } catch (Exception e) { + log.error("Failed to insert document with ID {}. Error: {}", document.getObjectId("id"), e.getMessage()); + continue; + } + + // Remove the document from the source collection + try { + sourceCollection.deleteOne(Filters.eq("_id", document.getObjectId("id"))); + } catch (Exception e) { + log.error("Failed to delete document with ID {}. Error: {}", document.getObjectId("id"), e.getMessage()); + continue; + } + + processedCount++; + log.info("Processed document {} / {}", processedCount, totalDocuments); + } + } catch (Exception e) { + log.error("Failed during archival process. Error: {}", e.getMessage()); + } + + log.info("Archival process completed. Total documents archived: {}", processedCount); + } + + private void archiveForVersionBelow5(Instant thresholdDate) { + log.info("Running archival for MongoDB version < 5"); + + MongoCollection sourceCollection = mongoTemplate.getDb().getCollection("applicationHistorySnapshotTS"); + + long totalDocuments = sourceCollection.countDocuments(Filters.lte("createdAt", thresholdDate)); + log.info("Total documents to archive: {}", totalDocuments); + + long processedCount = 0; + + try (MongoCursor cursor = sourceCollection.find(Filters.lte("createdAt", thresholdDate)).iterator()) { + while (cursor.hasNext()) { + Document document = cursor.next(); + + // Transform the document for the target collection + document.put("id", document.getObjectId("_id")); // Map `_id` to `id` + document.remove("_id"); + + // Use aggregation with $out for the single document + try { + sourceCollection.aggregate(List.of( + Filters.eq("_id", document.getObjectId("id")), + new Document("$project", new Document() + .append("applicationId", document.get("applicationId")) + .append("dsl", document.get("dsl")) + .append("context", document.get("context")) + .append("createdAt", document.get("createdAt")) + .append("createdBy", document.get("createdBy")) + .append("modifiedBy", document.get("modifiedBy")) + .append("updatedAt", document.get("updatedAt")) + .append("id", document.get("id"))), + new Document("$out", "applicationHistorySnapshot") + )).first(); + } catch (Exception e) { + log.error("Failed to aggregate and insert document with ID {}. Error: {}", document.getObjectId("id"), e.getMessage()); + continue; + } + + // Remove the document from the source collection + try { + sourceCollection.deleteOne(Filters.eq("_id", document.getObjectId("id"))); + } catch (Exception e) { + log.error("Failed to delete document with ID {}. Error: {}", document.getObjectId("id"), e.getMessage()); + continue; + } + + processedCount++; + log.info("Processed document {} / {}", processedCount, totalDocuments); + } + } catch (Exception e) { + log.error("Failed during archival process. Error: {}", e.getMessage()); + } + + log.info("Archival process completed. Total documents archived: {}", processedCount); + } } From f7a6179e51f25fd0d5cd8e74fe4e91febf7be2b3 Mon Sep 17 00:00:00 2001 From: Thomasr Date: Thu, 21 Nov 2024 12:42:26 -0500 Subject: [PATCH 06/36] modify snapshot task logic to move from normal collection to timeseries one. --- .../org/lowcoder/runner/migrations/DatabaseChangelog.java | 4 ++-- .../org/lowcoder/runner/task/ArchiveSnapshotTask.java | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java index 153fd765f..a51a74e09 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java @@ -329,7 +329,7 @@ public void addTimeSeriesSnapshotHistory(MongockTemplate mongoTemplate, CommonCo // Aggregation pipeline to fetch the records List aggregationPipeline = Arrays.asList( - new Document("$match", new Document("createdAt", new Document("$gte", thresholdDate))), + new Document("$match", new Document("createdAt", new Document("$lte", thresholdDate))), new Document("$project", new Document() .append("applicationId", 1) .append("dsl", 1) @@ -365,7 +365,7 @@ public void addTimeSeriesSnapshotHistory(MongockTemplate mongoTemplate, CommonCo // Aggregation pipeline with $out List aggregationPipeline = Arrays.asList( - new Document("$match", new Document("createdAt", new Document("$gte", thresholdDate))), + new Document("$match", new Document("createdAt", new Document("$lte", thresholdDate))), new Document("$project", new Document() .append("applicationId", 1) .append("dsl", 1) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/task/ArchiveSnapshotTask.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/task/ArchiveSnapshotTask.java index 1a2559ad9..28108f51a 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/task/ArchiveSnapshotTask.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/task/ArchiveSnapshotTask.java @@ -46,8 +46,8 @@ private int getMongoDBVersion() { private void archiveForVersion5AndAbove(Instant thresholdDate) { log.info("Running archival for MongoDB version >= 5"); - MongoCollection sourceCollection = mongoTemplate.getDb().getCollection("applicationHistorySnapshotTS"); - MongoCollection targetCollection = mongoTemplate.getDb().getCollection("applicationHistorySnapshot"); + MongoCollection sourceCollection = mongoTemplate.getDb().getCollection("applicationHistorySnapshot"); + MongoCollection targetCollection = mongoTemplate.getDb().getCollection("applicationHistorySnapshotTS"); long totalDocuments = sourceCollection.countDocuments(Filters.lte("createdAt", thresholdDate)); log.info("Total documents to archive: {}", totalDocuments); @@ -91,7 +91,7 @@ private void archiveForVersion5AndAbove(Instant thresholdDate) { private void archiveForVersionBelow5(Instant thresholdDate) { log.info("Running archival for MongoDB version < 5"); - MongoCollection sourceCollection = mongoTemplate.getDb().getCollection("applicationHistorySnapshotTS"); + MongoCollection sourceCollection = mongoTemplate.getDb().getCollection("applicationHistorySnapshot"); long totalDocuments = sourceCollection.countDocuments(Filters.lte("createdAt", thresholdDate)); log.info("Total documents to archive: {}", totalDocuments); @@ -119,7 +119,7 @@ private void archiveForVersionBelow5(Instant thresholdDate) { .append("modifiedBy", document.get("modifiedBy")) .append("updatedAt", document.get("updatedAt")) .append("id", document.get("id"))), - new Document("$out", "applicationHistorySnapshot") + new Document("$out", "applicationHistorySnapshotTS") )).first(); } catch (Exception e) { log.error("Failed to aggregate and insert document with ID {}. Error: {}", document.getObjectId("id"), e.getMessage()); From 138ebd86b0707f1a7127bbbb273239aeeb2aa0c1 Mon Sep 17 00:00:00 2001 From: Thomasr Date: Fri, 22 Nov 2024 03:21:05 -0500 Subject: [PATCH 07/36] swap ts and normal collection in api endpoint --- ...tionHistoryArchivedSnapshotRepository.java | 6 ++-- .../ApplicationHistorySnapshotRepository.java | 6 ++-- .../ApplicationHistorySnapshotService.java | 8 ++--- ...ApplicationHistorySnapshotServiceImpl.java | 18 ++++++------ .../ApplicationHistorySnapshotController.java | 29 ++++++++++--------- 5 files changed, 34 insertions(+), 33 deletions(-) diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationHistoryArchivedSnapshotRepository.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationHistoryArchivedSnapshotRepository.java index dded29c35..548d6e439 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationHistoryArchivedSnapshotRepository.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationHistoryArchivedSnapshotRepository.java @@ -1,6 +1,6 @@ package org.lowcoder.domain.application.repository; -import org.lowcoder.domain.application.model.ApplicationHistorySnapshot; +import org.lowcoder.domain.application.model.ApplicationHistorySnapshotTS; import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.Query; import org.springframework.data.mongodb.repository.ReactiveMongoRepository; @@ -11,7 +11,7 @@ import java.time.Instant; @Repository -public interface ApplicationHistoryArchivedSnapshotRepository extends ReactiveMongoRepository { +public interface ApplicationHistoryArchivedSnapshotRepository extends ReactiveMongoRepository { @Query(value = "{ 'applicationId': ?0, $and: [" + "{$or: [ { 'context.operations': { $elemMatch: { 'compName': ?1 } } }, { $expr: { $eq: [?1, null] } } ]}, " + @@ -20,7 +20,7 @@ public interface ApplicationHistoryArchivedSnapshotRepository extends ReactiveMo "{$or: [ { 'createdAt': { $lte: ?4} }, { $expr: { $eq: [?4, null] } } ] } " + "]}", fields = "{applicationId : 1, context: 1, createdBy : 1, createdAt : 1}") - Flux findAllByApplicationId(String applicationId, String compName, String theme, Instant createdAtFrom, Instant createdAtTo, Pageable pageable); + Flux findAllByApplicationId(String applicationId, String compName, String theme, Instant createdAtFrom, Instant createdAtTo, Pageable pageable); Mono countByApplicationId(String applicationId); } diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationHistorySnapshotRepository.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationHistorySnapshotRepository.java index 809decfd6..eabf2caf6 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationHistorySnapshotRepository.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationHistorySnapshotRepository.java @@ -1,6 +1,6 @@ package org.lowcoder.domain.application.repository; -import org.lowcoder.domain.application.model.ApplicationHistorySnapshotTS; +import org.lowcoder.domain.application.model.ApplicationHistorySnapshot; import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.Query; import org.springframework.data.mongodb.repository.ReactiveMongoRepository; @@ -11,7 +11,7 @@ import java.time.Instant; @Repository -public interface ApplicationHistorySnapshotRepository extends ReactiveMongoRepository { +public interface ApplicationHistorySnapshotRepository extends ReactiveMongoRepository { @Query(value = "{ 'applicationId': ?0, $and: [" + "{$or: [ { 'context.operations': { $elemMatch: { 'compName': ?1 } } }, { $expr: { $eq: [?1, null] } } ]}, " + @@ -20,7 +20,7 @@ public interface ApplicationHistorySnapshotRepository extends ReactiveMongoRepos "{$or: [ { 'createdAt': { $lte: ?4} }, { $expr: { $eq: [?4, null] } } ] } " + "]}", fields = "{applicationId : 1, context: 1, createdBy : 1, createdAt : 1}") - Flux findAllByApplicationId(String applicationId, String compName, String theme, Instant createdAtFrom, Instant createdAtTo, Pageable pageable); + Flux findAllByApplicationId(String applicationId, String compName, String theme, Instant createdAtFrom, Instant createdAtTo, Pageable pageable); Mono countByApplicationId(String applicationId); } diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationHistorySnapshotService.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationHistorySnapshotService.java index fd4a79f82..470bb63ff 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationHistorySnapshotService.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationHistorySnapshotService.java @@ -13,12 +13,12 @@ public interface ApplicationHistorySnapshotService { Mono createHistorySnapshot(String applicationId, Map dsl, Map context, String userId); - Mono> listAllHistorySnapshotBriefInfo(String applicationId, String compName, String theme, Instant from, Instant to, PageRequest pageRequest); - Mono> listAllHistorySnapshotBriefInfoArchived(String applicationId, String compName, String theme, Instant from, Instant to, PageRequest pageRequest); + Mono> listAllHistorySnapshotBriefInfo(String applicationId, String compName, String theme, Instant from, Instant to, PageRequest pageRequest); + Mono> listAllHistorySnapshotBriefInfoArchived(String applicationId, String compName, String theme, Instant from, Instant to, PageRequest pageRequest); Mono countByApplicationId(String applicationId); - Mono getHistorySnapshotDetail(String historySnapshotId); + Mono getHistorySnapshotDetail(String historySnapshotId); - Mono getHistorySnapshotDetailArchived(String historySnapshotId); + Mono getHistorySnapshotDetailArchived(String historySnapshotId); } diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/impl/ApplicationHistorySnapshotServiceImpl.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/impl/ApplicationHistorySnapshotServiceImpl.java index c47b39955..f797b9756 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/impl/ApplicationHistorySnapshotServiceImpl.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/impl/ApplicationHistorySnapshotServiceImpl.java @@ -29,24 +29,24 @@ public class ApplicationHistorySnapshotServiceImpl implements ApplicationHistory @Override public Mono createHistorySnapshot(String applicationId, Map dsl, Map context, String userId) { - ApplicationHistorySnapshotTS applicationHistorySnapshotTS = new ApplicationHistorySnapshotTS(); - applicationHistorySnapshotTS.setApplicationId(applicationId); - applicationHistorySnapshotTS.setDsl(dsl); - applicationHistorySnapshotTS.setContext(context); - return repository.save(applicationHistorySnapshotTS) + ApplicationHistorySnapshot applicationHistorySnapshot = new ApplicationHistorySnapshot(); + applicationHistorySnapshot.setApplicationId(applicationId); + applicationHistorySnapshot.setDsl(dsl); + applicationHistorySnapshot.setContext(context); + return repository.save(applicationHistorySnapshot) .thenReturn(true) .onErrorReturn(false); } @Override - public Mono> listAllHistorySnapshotBriefInfo(String applicationId, String compName, String theme, Instant from, Instant to, PageRequest pageRequest) { + public Mono> listAllHistorySnapshotBriefInfo(String applicationId, String compName, String theme, Instant from, Instant to, PageRequest pageRequest) { return repository.findAllByApplicationId(applicationId, compName, theme, from, to, pageRequest.withSort(Direction.DESC, "id")) .collectList() .onErrorMap(Exception.class, e -> ofException(BizError.FETCH_HISTORY_SNAPSHOT_FAILURE, "FETCH_HISTORY_SNAPSHOT_FAILURE")); } @Override - public Mono> listAllHistorySnapshotBriefInfoArchived(String applicationId, String compName, String theme, Instant from, Instant to, PageRequest pageRequest) { + public Mono> listAllHistorySnapshotBriefInfoArchived(String applicationId, String compName, String theme, Instant from, Instant to, PageRequest pageRequest) { return repositoryArchived.findAllByApplicationId(applicationId, compName, theme, from, to, pageRequest.withSort(Direction.DESC, "id")) .collectList() .onErrorMap(Exception.class, e -> ofException(BizError.FETCH_HISTORY_SNAPSHOT_FAILURE, "FETCH_HISTORY_SNAPSHOT_FAILURE")); @@ -61,14 +61,14 @@ public Mono countByApplicationId(String applicationId) { @Override - public Mono getHistorySnapshotDetail(String historySnapshotId) { + public Mono getHistorySnapshotDetail(String historySnapshotId) { return repository.findById(historySnapshotId) .switchIfEmpty(deferredError(INVALID_HISTORY_SNAPSHOT, "INVALID_HISTORY_SNAPSHOT", historySnapshotId)); } @Override - public Mono getHistorySnapshotDetailArchived(String historySnapshotId) { + public Mono getHistorySnapshotDetailArchived(String historySnapshotId) { return repositoryArchived.findById(historySnapshotId) .switchIfEmpty(deferredError(INVALID_HISTORY_SNAPSHOT, "INVALID_HISTORY_SNAPSHOT", historySnapshotId)); } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationHistorySnapshotController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationHistorySnapshotController.java index 6b6d94a51..682f60ae0 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationHistorySnapshotController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationHistorySnapshotController.java @@ -13,6 +13,7 @@ import org.lowcoder.domain.application.service.ApplicationService; import org.lowcoder.domain.permission.model.ResourceAction; import org.lowcoder.domain.permission.service.ResourcePermissionService; +import org.lowcoder.domain.user.model.User; import org.lowcoder.domain.user.service.UserService; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -69,15 +70,15 @@ public Mono>> listAllHistorySnapshotBriefInfo(@ .flatMap(__ -> applicationHistorySnapshotService.listAllHistorySnapshotBriefInfo(applicationId, compName, theme, from, to, pagination.toPageRequest())) .flatMap(snapshotList -> { Mono> snapshotBriefInfoList = multiBuild(snapshotList, - ApplicationHistorySnapshotTS::getCreatedBy, + ApplicationHistorySnapshot::getCreatedBy, userService::getByIds, - (applicationHistorySnapshotTS, user) -> new ApplicationHistorySnapshotBriefInfo( - applicationHistorySnapshotTS.getId(), - applicationHistorySnapshotTS.getContext(), - applicationHistorySnapshotTS.getCreatedBy(), + (applicationHistorySnapshot, user) -> new ApplicationHistorySnapshotBriefInfo( + applicationHistorySnapshot.getId(), + applicationHistorySnapshot.getContext(), + applicationHistorySnapshot.getCreatedBy(), user.getName(), user.getAvatarUrl(), - applicationHistorySnapshotTS.getCreatedAt().toEpochMilli() + applicationHistorySnapshot.getCreatedAt().toEpochMilli() ) ); @@ -106,15 +107,15 @@ public Mono>> listAllHistorySnapshotBriefInfoAr .flatMap(__ -> applicationHistorySnapshotService.listAllHistorySnapshotBriefInfoArchived(applicationId, compName, theme, from, to, pagination.toPageRequest())) .flatMap(snapshotList -> { Mono> snapshotBriefInfoList = multiBuild(snapshotList, - ApplicationHistorySnapshot::getCreatedBy, + ApplicationHistorySnapshotTS::getCreatedBy, userService::getByIds, - (applicationHistorySnapshot, user) -> new ApplicationHistorySnapshotBriefInfo( - applicationHistorySnapshot.getId(), - applicationHistorySnapshot.getContext(), - applicationHistorySnapshot.getCreatedBy(), + (applicationHistorySnapshotTS, user) -> new ApplicationHistorySnapshotBriefInfo( + applicationHistorySnapshotTS.getId(), + applicationHistorySnapshotTS.getContext(), + applicationHistorySnapshotTS.getCreatedBy(), user.getName(), user.getAvatarUrl(), - applicationHistorySnapshot.getCreatedAt().toEpochMilli() + applicationHistorySnapshotTS.getCreatedAt().toEpochMilli() ) ); @@ -133,7 +134,7 @@ public Mono> getHistorySnapshotDsl(@PathVar .delayUntil(visitor -> resourcePermissionService.checkResourcePermissionWithError(visitor, applicationId, ResourceAction.EDIT_APPLICATIONS)) .flatMap(__ -> applicationHistorySnapshotService.getHistorySnapshotDetail(snapshotId)) - .map(ApplicationHistorySnapshotTS::getDsl) + .map(ApplicationHistorySnapshot::getDsl) .zipWhen(applicationService::getAllDependentModulesFromDsl) .map(tuple -> { Map applicationDsl = tuple.getT1(); @@ -155,7 +156,7 @@ public Mono> getHistorySnapshotDslArchived( .delayUntil(visitor -> resourcePermissionService.checkResourcePermissionWithError(visitor, applicationId, ResourceAction.EDIT_APPLICATIONS)) .flatMap(__ -> applicationHistorySnapshotService.getHistorySnapshotDetailArchived(snapshotId)) - .map(ApplicationHistorySnapshot::getDsl) + .map(ApplicationHistorySnapshotTS::getDsl) .zipWhen(applicationService::getAllDependentModulesFromDsl) .map(tuple -> { Map applicationDsl = tuple.getT1(); From a28b90f24b14edaae74c0caf38ef96e6ff182a6b Mon Sep 17 00:00:00 2001 From: Thomasr Date: Fri, 22 Nov 2024 03:50:46 -0500 Subject: [PATCH 08/36] application snapshot history count logic fix --- .../service/ApplicationHistorySnapshotService.java | 1 + .../impl/ApplicationHistorySnapshotServiceImpl.java | 7 +++++++ .../application/ApplicationHistorySnapshotController.java | 6 +++--- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationHistorySnapshotService.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationHistorySnapshotService.java index 470bb63ff..f4e5b3fcf 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationHistorySnapshotService.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationHistorySnapshotService.java @@ -17,6 +17,7 @@ public interface ApplicationHistorySnapshotService { Mono> listAllHistorySnapshotBriefInfoArchived(String applicationId, String compName, String theme, Instant from, Instant to, PageRequest pageRequest); Mono countByApplicationId(String applicationId); + Mono countByApplicationIdArchived(String applicationId); Mono getHistorySnapshotDetail(String historySnapshotId); diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/impl/ApplicationHistorySnapshotServiceImpl.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/impl/ApplicationHistorySnapshotServiceImpl.java index f797b9756..2d4aba44a 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/impl/ApplicationHistorySnapshotServiceImpl.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/impl/ApplicationHistorySnapshotServiceImpl.java @@ -59,6 +59,13 @@ public Mono countByApplicationId(String applicationId) { e -> ofException(BizError.FETCH_HISTORY_SNAPSHOT_COUNT_FAILURE, "FETCH_HISTORY_SNAPSHOT_COUNT_FAILURE")); } + @Override + public Mono countByApplicationIdArchived(String applicationId) { + return repositoryArchived.countByApplicationId(applicationId) + .onErrorMap(Exception.class, + e -> ofException(BizError.FETCH_HISTORY_SNAPSHOT_COUNT_FAILURE, "FETCH_HISTORY_SNAPSHOT_COUNT_FAILURE")); + } + @Override public Mono getHistorySnapshotDetail(String historySnapshotId) { diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationHistorySnapshotController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationHistorySnapshotController.java index 682f60ae0..b5a6381d7 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationHistorySnapshotController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationHistorySnapshotController.java @@ -55,7 +55,7 @@ public Mono> create(@RequestBody ApplicationHistorySnapsho @Override public Mono>> listAllHistorySnapshotBriefInfo(@PathVariable String applicationId, - @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size, @RequestParam String compName, @RequestParam String theme, @@ -92,7 +92,7 @@ public Mono>> listAllHistorySnapshotBriefInfo(@ @Override public Mono>> listAllHistorySnapshotBriefInfoArchived(@PathVariable String applicationId, - @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size, @RequestParam String compName, @RequestParam String theme, @@ -119,7 +119,7 @@ public Mono>> listAllHistorySnapshotBriefInfoAr ) ); - Mono applicationHistorySnapshotCount = applicationHistorySnapshotService.countByApplicationId(applicationId); + Mono applicationHistorySnapshotCount = applicationHistorySnapshotService.countByApplicationIdArchived(applicationId); return Mono.zip(snapshotBriefInfoList, applicationHistorySnapshotCount) .map(tuple -> ImmutableMap.of("list", tuple.getT1(), "count", tuple.getT2())); From 311bae778d2a730a5c7584f0831976c044918b9d Mon Sep 17 00:00:00 2001 From: Thomasr Date: Fri, 22 Nov 2024 03:55:27 -0500 Subject: [PATCH 09/36] page number start from 1 --- .../org/lowcoder/api/application/ApplicationController.java | 4 ++-- .../org/lowcoder/api/application/ApplicationEndpoints.java | 2 +- .../main/java/org/lowcoder/api/bundle/BundleController.java | 2 +- .../main/java/org/lowcoder/api/bundle/BundleEndpoints.java | 2 +- .../src/main/java/org/lowcoder/api/home/FolderController.java | 4 ++-- .../src/main/java/org/lowcoder/api/home/FolderEndpoints.java | 2 +- .../java/org/lowcoder/api/usermanagement/GroupController.java | 4 ++-- .../java/org/lowcoder/api/usermanagement/GroupEndpoints.java | 2 +- .../lowcoder/api/usermanagement/OrganizationController.java | 2 +- .../lowcoder/api/usermanagement/OrganizationEndpoints.java | 2 +- .../src/main/java/org/lowcoder/api/util/Pagination.java | 2 +- 11 files changed, 14 insertions(+), 14 deletions(-) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java index ed7079598..3c91f7ce7 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java @@ -162,12 +162,12 @@ public Mono>> getApplications(@RequestPar @RequestParam(required = false) ApplicationStatus applicationStatus, @RequestParam(defaultValue = "true") boolean withContainerSize, @RequestParam(required = false) String name, - @RequestParam(required = false, defaultValue = "0") Integer pageNum, + @RequestParam(required = false, defaultValue = "1") Integer pageNum, @RequestParam(required = false, defaultValue = "0") Integer pageSize) { ApplicationType applicationTypeEnum = applicationType == null ? null : ApplicationType.fromValue(applicationType); var flux = userHomeApiService.getAllAuthorisedApplications4CurrentOrgMember(applicationTypeEnum, applicationStatus, withContainerSize, name).cache(); Mono countMono = flux.count(); - var flux1 = flux.skip((long) pageNum * pageSize); + var flux1 = flux.skip((long) (pageNum - 1) * pageSize); if(pageSize > 0) flux1 = flux1.take(pageSize); return flux1.collectList().zipWith(countMono) .map(tuple -> PageResponseView.success(tuple.getT1(), pageNum, pageSize, Math.toIntExact(tuple.getT2()))); diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationEndpoints.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationEndpoints.java index 4eed69ee2..cef119847 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationEndpoints.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationEndpoints.java @@ -167,7 +167,7 @@ public Mono>> getApplications(@RequestPar @RequestParam(required = false) ApplicationStatus applicationStatus, @RequestParam(defaultValue = "true") boolean withContainerSize, @RequestParam(required = false) String name, - @RequestParam(required = false, defaultValue = "0") Integer pageNum, + @RequestParam(required = false, defaultValue = "1") Integer pageNum, @RequestParam(required = false, defaultValue = "0") Integer pageSize); @Operation( diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/bundle/BundleController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/bundle/BundleController.java index 254e78037..cb0df9241 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/bundle/BundleController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/bundle/BundleController.java @@ -106,7 +106,7 @@ public Mono>> getRecycledBundles() { @Override public Mono> getElements(@PathVariable String bundleId, @RequestParam(value = "applicationType", required = false) ApplicationType applicationType, - @RequestParam(required = false, defaultValue = "0") Integer pageNum, + @RequestParam(required = false, defaultValue = "1") Integer pageNum, @RequestParam(required = false, defaultValue = "0") Integer pageSize) { String objectId = gidService.convertBundleIdToObjectId(bundleId); var flux = bundleApiService.getElements(objectId, applicationType).cache(); diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/bundle/BundleEndpoints.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/bundle/BundleEndpoints.java index 8674c62b5..8d668c1b7 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/bundle/BundleEndpoints.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/bundle/BundleEndpoints.java @@ -123,7 +123,7 @@ public interface BundleEndpoints @GetMapping("/{bundleId}/elements") public Mono> getElements(@PathVariable String bundleId, @RequestParam(value = "applicationType", required = false) ApplicationType applicationType, - @RequestParam(required = false, defaultValue = "0") Integer pageNum, + @RequestParam(required = false, defaultValue = "1") Integer pageNum, @RequestParam(required = false, defaultValue = "0") Integer pageSize); @Operation( diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderController.java index 31cf49494..24a77dd29 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderController.java @@ -70,12 +70,12 @@ public Mono> update(@RequestBody Folder folder) { public Mono> getElements(@RequestParam(value = "id", required = false) String folderId, @RequestParam(value = "applicationType", required = false) ApplicationType applicationType, @RequestParam(required = false) String name, - @RequestParam(required = false, defaultValue = "0") Integer pageNum, + @RequestParam(required = false, defaultValue = "1") Integer pageNum, @RequestParam(required = false, defaultValue = "0") Integer pageSize) { String objectId = gidService.convertFolderIdToObjectId(folderId); var flux = folderApiService.getElements(objectId, applicationType, name).cache(); var countMono = flux.count(); - var flux1 = flux.skip((long) pageNum * pageSize); + var flux1 = flux.skip((long) (pageNum - 1) * pageSize); if(pageSize > 0) flux1 = flux1.take(pageSize); return flux1.collectList() .delayUntil(__ -> folderApiService.upsertLastViewTime(objectId)) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderEndpoints.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderEndpoints.java index 43e5ce785..2c6279084 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderEndpoints.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderEndpoints.java @@ -71,7 +71,7 @@ public interface FolderEndpoints public Mono> getElements(@RequestParam(value = "id", required = false) String folderId, @RequestParam(value = "applicationType", required = false) ApplicationType applicationType, @RequestParam(required = false) String name, - @RequestParam(required = false, defaultValue = "0") Integer pageNum, + @RequestParam(required = false, defaultValue = "1") Integer pageNum, @RequestParam(required = false, defaultValue = "0") Integer pageSize); @Operation( diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupController.java index d478bcfc2..4e7facb99 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupController.java @@ -75,7 +75,7 @@ public Mono> delete(@PathVariable String groupId) { } @Override - public Mono>> getOrgGroups(@RequestParam(required = false, defaultValue = "0") Integer pageNum, + public Mono>> getOrgGroups(@RequestParam(required = false, defaultValue = "1") Integer pageNum, @RequestParam(required = false, defaultValue = "0") Integer pageSize) { return groupApiService.getGroups().flatMap(groupList -> { if(groupList.isEmpty()) return Mono.just(new GroupListResponseView<>(ResponseView.SUCCESS, @@ -99,7 +99,7 @@ public Mono>> getOrgGroups(@RequestParam(r .filter(orgMember -> !orgMember.isAdmin() && !orgMember.isSuperAdmin() && devMembers.stream().noneMatch(devMember -> devMember.getUserId().equals(orgMember.getUserId()))).toList().size(); - var subList = groupList.subList(pageNum * pageSize, pageSize <= 0?groupList.size():pageNum * pageSize + pageSize); + var subList = groupList.subList((pageNum - 1) * pageSize, pageSize <= 0?groupList.size():pageNum * pageSize); return new GroupListResponseView<>(ResponseView.SUCCESS, "", subList, diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupEndpoints.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupEndpoints.java index 4f0825333..e2f8bfa7a 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupEndpoints.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupEndpoints.java @@ -63,7 +63,7 @@ public Mono> update(@PathVariable String groupId, description = "Retrieve a list of User Groups within Lowcoder, providing an overview of available groups, based on the access rights of the currently impersonated User." ) @GetMapping("/list") - public Mono>> getOrgGroups(@RequestParam(required = false, defaultValue = "0") Integer pageNum, + public Mono>> getOrgGroups(@RequestParam(required = false, defaultValue = "1") Integer pageNum, @RequestParam(required = false, defaultValue = "0") Integer pageSize); @Operation( diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationController.java index b0acc8cf1..2b2a9dd75 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationController.java @@ -50,7 +50,7 @@ public class OrganizationController implements OrganizationEndpoints @Override public Mono> getOrganizationByUser(@PathVariable String email, - @RequestParam(required = false, defaultValue = "0") Integer pageNum, + @RequestParam(required = false, defaultValue = "1") Integer pageNum, @RequestParam(required = false, defaultValue = "0") Integer pageSize) { var flux = userService.findByEmailDeep(email).flux().flatMap(user -> orgMemberService.getAllActiveOrgs(user.getId())) .flatMap(orgMember -> organizationService.getById(orgMember.getOrgId())) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationEndpoints.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationEndpoints.java index 734012033..38332e892 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationEndpoints.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationEndpoints.java @@ -46,7 +46,7 @@ public interface OrganizationEndpoints ) @GetMapping("/byuser/{email}") public Mono> getOrganizationByUser(@PathVariable String email, - @RequestParam(required = false, defaultValue = "0") Integer pageNum, + @RequestParam(required = false, defaultValue = "1") Integer pageNum, @RequestParam(required = false, defaultValue = "0") Integer pageSize); @Operation( diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/Pagination.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/Pagination.java index 051c3e006..03141d6bb 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/Pagination.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/Pagination.java @@ -64,7 +64,7 @@ public int size() { @NotNull public static Mono> fluxToPageResponseView(Integer pageNum, Integer pageSize, Flux flux) { var countMono = flux.count(); - var flux1 = flux.skip((long) pageNum * pageSize); + var flux1 = flux.skip((long) (pageNum - 1) * pageSize); if(pageSize > 0) flux1 = flux1.take(pageSize); return flux1.collectList().zipWith(countMono) .map(tuple -> PageResponseView.success(tuple.getT1(), pageNum, pageSize, Math.toIntExact(tuple.getT2()))); From 570e35cdf95d844c96068c31d63596592a2e76bd Mon Sep 17 00:00:00 2001 From: Thomasr Date: Fri, 22 Nov 2024 12:04:20 -0500 Subject: [PATCH 10/36] #1284 Fixed duplicate key error for currentUser endpoint when logging to duplicate keycloak --- .../java/org/lowcoder/domain/user/service/UserServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/user/service/UserServiceImpl.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/user/service/UserServiceImpl.java index 6b800720c..981000caf 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/user/service/UserServiceImpl.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/user/service/UserServiceImpl.java @@ -418,7 +418,7 @@ protected Map convertConnections(Set connections) { return connections.stream() .filter(connection -> !AuthSourceConstants.EMAIL.equals(connection.getSource()) && !AuthSourceConstants.PHONE.equals(connection.getSource())) - .collect(Collectors.toMap(Connection::getSource, Connection::getRawUserInfo)); + .collect(Collectors.toMap(Connection::getAuthId, Connection::getRawUserInfo)); } protected String convertEmail(Set connections) { From d6d7a88789682a824788f44bf44303a6ab32ccf2 Mon Sep 17 00:00:00 2001 From: Thomasr Date: Fri, 22 Nov 2024 13:57:27 -0500 Subject: [PATCH 11/36] fix test compile issue --- .../impl/ApplicationHistorySnapshotServiceTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/service/impl/ApplicationHistorySnapshotServiceTest.java b/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/service/impl/ApplicationHistorySnapshotServiceTest.java index fb7109134..81c0cb56d 100644 --- a/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/service/impl/ApplicationHistorySnapshotServiceTest.java +++ b/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/service/impl/ApplicationHistorySnapshotServiceTest.java @@ -4,7 +4,7 @@ import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; -import org.lowcoder.domain.application.model.ApplicationHistorySnapshotTS; +import org.lowcoder.domain.application.model.ApplicationHistorySnapshot; import org.lowcoder.domain.application.service.ApplicationHistorySnapshotService; import org.lowcoder.sdk.models.HasIdAndAuditing; import org.springframework.beans.factory.annotation.Autowired; @@ -47,8 +47,8 @@ public void testServiceMethods() { .assertNext(list -> { assertEquals(2, list.size()); - ApplicationHistorySnapshotTS first = list.get(0); - ApplicationHistorySnapshotTS second = list.get(1); + ApplicationHistorySnapshot first = list.get(0); + ApplicationHistorySnapshot second = list.get(1); assertTrue(first.getCreatedAt().isAfter(second.getCreatedAt())); assertNull(first.getDsl()); @@ -66,7 +66,7 @@ public void testServiceMethods() { StepVerifier.create(service.listAllHistorySnapshotBriefInfo(applicationId, null, null, null, null, PageRequest.of(1, 1))) .assertNext(list -> { assertEquals(1, list.size()); - ApplicationHistorySnapshotTS one = list.get(0); + ApplicationHistorySnapshot one = list.get(0); assertNull(one.getDsl()); assertEquals(ImmutableMap.of("context", "context1"), one.getContext()); assertEquals(applicationId, one.getApplicationId()); From a740d0f973fde05308c566f3a2f56e844dec2a3b Mon Sep 17 00:00:00 2001 From: Imiss-U1025 Date: Tue, 19 Nov 2024 04:47:53 -0500 Subject: [PATCH 12/36] Added tree structure basically. --- .../src/pages/editor/right/ModulePanel.tsx | 620 +++++++++++++++--- 1 file changed, 518 insertions(+), 102 deletions(-) diff --git a/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx b/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx index dc4ad3cc9..7655ad57b 100644 --- a/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx +++ b/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx @@ -1,80 +1,163 @@ -import CreateAppButton from "components/CreateAppButton"; -import { EmptyContent } from "components/EmptyContent"; -import { ApplicationMeta, AppTypeEnum } from "constants/applicationConstants"; -import { APPLICATION_VIEW_URL } from "constants/routesURL"; +import { ApplicationMeta, AppTypeEnum, FolderMeta } from "constants/applicationConstants"; import { - ActiveTextColor, - BorderActiveShadowColor, - BorderColor, - GreyTextColor, + BorderActiveColor, + NormalMenuIconColor, } from "constants/style"; -import { ModuleDocIcon } from "lowcoder-design"; +import { APPLICATION_VIEW_URL } from "constants/routesURL"; +import { RightContext } from "./rightContext"; +import { + EditPopover, + EditText, + FoldedIcon, + ModuleDocIcon, + PointIcon, + PopupCard, + UnfoldIcon, + FileFolderIcon +} from "lowcoder-design"; import { trans } from "i18n"; import { draggingUtils } from "layout/draggingUtils"; -import { useContext, useEffect } from "react"; +import {CSSProperties, useContext, useEffect, useState} from "react"; import { useDispatch, useSelector } from "react-redux"; import { fetchAllModules } from "redux/reduxActions/applicationActions"; import styled from "styled-components"; +import CreateAppButton from "components/CreateAppButton"; import { TransparentImg } from "util/commonUtils"; -import { ExternalEditorContext } from "util/context/ExternalEditorContext"; -import { formatTimestamp } from "util/dateTimeUtils"; -import { RightContext } from "./rightContext"; -import { modulesSelector } from "../../../redux/selectors/applicationSelector"; -import { ComListTitle, ExtensionContentWrapper } from "./styledComponent"; - +import { ComListTitle } from "./styledComponent"; +import {folderElementsSelector} from "@lowcoder-ee/redux/selectors/folderSelector"; +import {DraggableTree} from "@lowcoder-ee/components/DraggableTree/DraggableTree"; +import {EditorContext} from "lowcoder-sdk"; +import {showAppSnapshotSelector} from "@lowcoder-ee/redux/selectors/appSnapshotSelector"; +import {DraggableTreeNode, DraggableTreeNodeItemRenderProps} from "@lowcoder-ee/components/DraggableTree/types"; +import RefTreeComp from "@lowcoder-ee/comps/comps/refTreeComp"; +import { EmptyContent } from "components/EmptyContent"; const ItemWrapper = styled.div` + display: flex; + flex-direction: row; + &:last-child { + margin-bottom: 0; + } + .module-icon { + display: flex; - flex-direction: row; - margin-bottom: 12px; - &:last-child { - margin-bottom: 0; + justify-content: center; + align-items: center; + margin: 4px; + } + .module-content { + flex: 1; + display: flex; + flex-direction: column; + justify-content: space-around; + overflow: hidden; + } + .module-name { + line-height: 1.5; + font-size: 13px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } +`; + +type NodeType = { + name: string; + id: string; + isFolder: boolean; + containerSize?: { height: number; width: number }; + module?: ApplicationMeta; + children: NodeType[]; + rename: (val: string) => string + checkName: (val: string) => string +}; + + + +function buildTree(elementRecord: Record>): NodeType { + const elements = elementRecord[""]; + const elementMap: Record = {}; + let rootNode: NodeType = { + name: "", + id: "", + isFolder: true, + children: [], + rename: val => rootNode.name = val, + checkName: val => val } - &:hover { - cursor: grab; - .module-icon { - box-shadow: 0 0 5px 0 rgba(49, 94, 251, 0.15); - border-color: ${BorderActiveShadowColor}; - transform: scale(1.2); - } - .module-name { - color: ${ActiveTextColor}; + + // Initialize all folders and applications as NodeType + for (const element of elements) { + if (element.folder) { + elementMap[element.folderId] = { + name: element.name, + id: element.folderId, + isFolder: true, + children: [], + rename: val => elementMap[element.folderId].name = val, + checkName: val => val + }; + + // Process subapplications inside the folder + for (const app of element.subApplications || []) { + if (app.applicationType === AppTypeEnum.Module) { + const appNode: NodeType = { + name: app.name, + id: app.applicationId, + containerSize: app.containerSize, + isFolder: false, + children: [], + module: app, + rename: val => appNode.name = val, + checkName: val => val + }; + elementMap[element.folderId].children.push(appNode); // Add applications as children of the folder + } + } + } else { + if (element.applicationType === AppTypeEnum.Module) { + elementMap[element.applicationId] = { + name: element.name, + containerSize: element.containerSize, + id: element.applicationId, + isFolder: false, + children: [], + module: element, + rename: val => elementMap[element.applicationId].name = val, + checkName: val => val + }; + } } } - .module-icon { - transition: all 200ms linear; - margin-right: 8px; - width: 40px; - height: 40px; - display: flex; - justify-content: center; - align-items: center; - border: 1px solid ${BorderColor}; - border-radius: 4px; - } - .module-content { - flex: 1; - display: flex; - flex-direction: column; - justify-content: space-around; - overflow: hidden; - } - .module-name { - line-height: 1.5; - font-size: 13px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - .module-desc { - line-height: 1.5; - font-size: 12px; - color: ${GreyTextColor}; + + // Build the tree structure + for (const element of elements) { + if (element.folder) { + const parentId = element.parentFolderId; + if (parentId && elementMap[parentId]) { + elementMap[parentId].children.push(elementMap[element.folderId]); + } else { + rootNode.children.push(elementMap[element.folderId]); + } + } else if (elementMap[element.applicationId]) { + rootNode.children.push(elementMap[element.applicationId]); + } } -`; + console.log(rootNode.children.sort((a, b) => { + if (a.isFolder && !b.isFolder) { + return -1; // a is a isFolder and should come first + } else if (!a.isFolder && b.isFolder) { + return 1; // b is a folder and should come first + } else { + return 0; // both are folders or both are not, keep original order + } + })); + return rootNode; +} + interface ModuleItemProps { - meta: ApplicationMeta; - onDrag: (type: string) => void; + meta: ApplicationMeta; + onDrag: (type: string) => void; } function ModuleItem(props: ModuleItemProps) { @@ -84,6 +167,7 @@ function ModuleItem(props: ModuleItemProps) { { + console.log(meta); e.dataTransfer.setData("compType", compType); e.dataTransfer.setDragImage(TransparentImg, 0, 0); draggingUtils.setData("compType", compType); @@ -100,57 +184,389 @@ function ModuleItem(props: ModuleItemProps) { }} >
- +
{props.meta.name}
-
{formatTimestamp(props.meta.createAt)}
); } -export default function ModulePanel() { - const dispatch = useDispatch(); - const modules = useSelector(modulesSelector); - const { onDrag, searchValue } = useContext(RightContext); - const { applicationId } = useContext(ExternalEditorContext); - - useEffect(() => { - dispatch(fetchAllModules({})); - }, [dispatch]); - - const filteredModules = modules.filter((i) => { - if (i.applicationId === applicationId || i.applicationType !== AppTypeEnum.Module) { - return false; +const HighlightBorder = styled.div<{ $active: boolean; $foldable: boolean; $level: number }>` + max-width: 100%; + flex: 1; + display: flex; + padding-left: ${(props) => props.$level * 20 + (props.$foldable ? 0 : 14)}px; + border-radius: 4px; + border: 1px solid ${(props) => (props.$active ? BorderActiveColor : "transparent")}; + align-items: center; + justify-content: center; +`; + +interface ColumnDivProps { + $color?: boolean; + $isOverlay: boolean; +} + +const ColumnDiv = styled.div` + width: 100%; + height: 25px; + display: flex; + user-select: none; + padding-left: 2px; + padding-right: 15px; + /* background-color: #ffffff; */ + /* margin: 2px 0; */ + background-color: ${(props) => (props.$isOverlay ? "rgba(255, 255, 255, 0.11)" : "")}; + + &&& { + background-color: ${(props) => (props.$color && !props.$isOverlay ? "#f2f7fc" : null)}; + } + + &:hover { + background-color: #f2f7fc80; + cursor: pointer; + } + + .taco-edit-text-wrapper { + width: 100%; + height: 21px; + line-height: 21px; + color: #222222; + margin-left: 0; + font-size: 13px; + padding-left: 0; + + &:hover { + background-color: transparent; + } + } + + .taco-edit-text-input { + width: 100%; + height: 21px; + line-height: 21px; + color: #222222; + margin-left: 0; + font-size: 13px; + background-color: #fdfdfd; + border: 1px solid #3377ff; + border-radius: 2px; + + &:focus { + border-color: #3377ff; + box-shadow: 0 0 0 2px #d6e4ff; + } + } +`; + +const FoldIconBtn = styled.div` + width: 12px; + height: 12px; + display: flex; + margin-right: 2px; +`; + +const Icon = styled(PointIcon)` + width: 16px; + height: 16px; + cursor: pointer; + flex-shrink: 0; + color: ${NormalMenuIconColor}; + + &:hover { + color: #315efb; + } +`; + +interface ModuleSidebarItemProps extends DraggableTreeNodeItemRenderProps { + id: string; + resComp: NodeType; + onCopy: () => void; + onSelect: () => void; + onDelete: () => void; + onToggleFold: () => void; +} + +const empty = ( + +

{trans("rightPanel.emptyModules")}

+ { + const appId = app.applicationInfoView.applicationId; + const url = APPLICATION_VIEW_URL(appId, "edit"); + window.open(url); + }} + /> + } - return i.name?.toLowerCase()?.includes(searchValue.trim()?.toLowerCase()) || !searchValue?.trim(); - }); - - const items = filteredModules.map((i) => ( - - )); - const empty = ( - -

{trans("rightPanel.emptyModules")}

- { - const appId = app.applicationInfoView.applicationId; - const url = APPLICATION_VIEW_URL(appId, "edit"); - window.open(url); - }} - /> - + /> +); + +function ModuleSidebarItem(props: ModuleSidebarItemProps) { + const { + id, + resComp, + isOver, + isOverlay, + path, + isFolded, + onDelete, + onCopy, + onSelect, + onToggleFold, + } = props; + const { onDrag } = useContext(RightContext); + const [error, setError] = useState(undefined); + const [editing, setEditing] = useState(false); + const editorState = useContext(EditorContext); + const readOnly = useSelector(showAppSnapshotSelector); + const [selectedModuleResName, setSelectedModuleResName] = useState(""); + const [selectedModuleResType, setSelectedModuleResType] = useState(false); + const level = path.length - 1; + const type = resComp.isFolder; + const name = resComp.name; + const icon = resComp.isFolder? : ; + const isSelected = type === selectedModuleResType && id === selectedModuleResName; + const isFolder = type; + + const handleFinishRename = (value: string) => { + let success = false; + let compId = name; + if (resComp.rename) { + compId = resComp.rename(value); + success = !!compId; + } else { + compId = name; + success = true; + } + if (success) { + setSelectedModuleResName(compId); + setSelectedModuleResType(type); + setError(undefined); + } + }; + + const handleNameChange = (value: string) => { + let err = ""; + if (resComp.checkName) { + err = resComp.checkName(value); + } else { + err = editorState.checkRename(name, value); + } + setError(err); + }; + + const handleClickItem = () => { + if (isFolder) { + onToggleFold(); + } + onSelect(); + }; + + return ( + + + {isFolder && {!isFolded ? : }} + {isFolder ? + <> + +
+ setEditing(editing)} + /> + +
: } - /> + {!readOnly && !isOverlay && ( + + + + )} +
+
); +} + +export default function ModulePanel() { + const dispatch = useDispatch(); + const elements = useSelector(folderElementsSelector); + const { onDrag, searchValue } = useContext(RightContext); + + useEffect(() => { + dispatch(fetchAllModules({})); + }, [dispatch]); + + + //Convert elements into tree + const tree = buildTree(elements); + const getByIdFromNode = (root: NodeType | null, id: string): NodeType | undefined => { + if (!root) { + return; + } + + if (root.id === id) { + return root; + } + + for (const child of root.children) { + const result = getByIdFromNode(child, id); + if (result) { + return result; + } + } + return; + } + + const getById = (id: string): NodeType | undefined => getByIdFromNode(tree, id); + const convertRefTree = (treeNode: NodeType) => { + const moduleResComp = getById(treeNode.id); + const currentNodeType = moduleResComp?.isFolder; + const childrenItems = treeNode.children + .map((i) => convertRefTree(i as NodeType)) + .filter((i): i is DraggableTreeNode => !!i); + const node: DraggableTreeNode = { + id: moduleResComp?.id, + canDropBefore: (source) => { + if (currentNodeType) { + return source?.isFolder!; + } + + return !source?.isFolder; + }, + canDropAfter: (source) => { + if ( + !currentNodeType && + source?.isFolder + ) { + return false; + } + return true; + }, + canDropIn: (source) => { + if (!currentNodeType) { + return false; + } + if (!source) { + return true; + } + if (source.isFolder) { + return false; + } + return true; + }, + items: childrenItems, + data: moduleResComp, + addSubItem(value) { + console.log("addSubItem", node.id, value, node); + // const pushAction = node.items.pushAction({ value: value.id() }); + // node.items.dispatch(pushAction); + }, + deleteItem(index) { + console.log("deleteItem", node.id, index); + // const deleteAction = node.children.items.deleteAction(index); + // node.children.items.dispatch(deleteAction); + }, + addItem(value) { + console.log("addItem", node.id, value); + // const pushAction = node.children.items.pushAction({ value: value.id() }); + // node.children.items.dispatch(pushAction); + }, + moveItem(from, to) { + console.log("node", node); + // const moveAction = node.children.items.arrayMoveAction(from, to); + // node.children.items.dispatch(moveAction); + }, + }; + + if ( + searchValue && + moduleResComp && + !moduleResComp.name.toLowerCase().includes(searchValue.toLowerCase()) && + childrenItems.length === 0 + ) { + return; + } + return node; + }; + + const node = convertRefTree(tree); + + function onCopy(type: boolean, id: string) { + console.log("onCopy", type, id); + } + + function onSelect(type: boolean, id: string, meta: any) { + console.log("onSelect", type, id, meta) + return + } + + function onDelete(type: boolean, id: string) { + console.log("onDelete", type, id); + return true; + } + return ( - <> - {trans("rightPanel.moduleListTitle")} - {items.length > 0 ? items : empty} - - ); + <> + {trans("rightPanel.moduleListTitle")} + {node ? + node={node!} + disable={!!searchValue} + unfoldAll={!!searchValue} + showSubInDragOverlay={false} + showDropInPositionLine={false} + showPositionLineDot + positionLineDotDiameter={4} + positionLineHeight={1} + itemHeight={25} + positionLineIndent={(path, dropInAsSub) => { + const indent = 2 + (path.length - 1) * 30; + if (dropInAsSub) { + return indent + 12; + } + return indent; + }} + renderItemContent={(params) => { + const { node, onToggleFold, onDelete: onDeleteTreeItem, ...otherParams } = params; + const resComp = node.data; + if (!resComp) { + return null; + } + const id = resComp.id; + const isFolder = resComp.isFolder; + return ( + onCopy(isFolder, id)} + onSelect={() => onSelect(isFolder, id, resComp)} + onDelete={() => { + if (onDelete(isFolder, id)) { + onDeleteTreeItem(); + } + }} + {...otherParams} + /> + ); + }} + /> : empty} + + ); } From 69c741568e53d25824ed47abf6e726aacf35103f Mon Sep 17 00:00:00 2001 From: Imiss-U1025 Date: Thu, 21 Nov 2024 05:13:45 -0500 Subject: [PATCH 13/36] Added Movefolder in redux. --- .../src/constants/reduxActionConstants.ts | 2 +- .../reducers/uiReducers/folderReducer.ts | 40 ++++++++++++++++++- .../src/redux/reduxActions/folderActions.ts | 1 + .../lowcoder/src/redux/sagas/folderSagas.ts | 6 ++- 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/client/packages/lowcoder/src/constants/reduxActionConstants.ts b/client/packages/lowcoder/src/constants/reduxActionConstants.ts index be3cd6271..6df5991f2 100644 --- a/client/packages/lowcoder/src/constants/reduxActionConstants.ts +++ b/client/packages/lowcoder/src/constants/reduxActionConstants.ts @@ -9,7 +9,7 @@ export const ReduxActionTypes = { FETCH_RAW_CURRENT_USER_SUCCESS: "FETCH_RAW_CURRENT_USER_SUCCESS", FETCH_API_KEYS: "FETCH_API_KEYS", FETCH_API_KEYS_SUCCESS: "FETCH_API_KEYS_SUCCESS", - + MOVE_TO_FOLDER2_SUCCESS: "MOVE_TO_FOLDER2_SUCCESS", /* plugin RELATED */ FETCH_DATA_SOURCE_TYPES: "FETCH_DATA_SOURCE_TYPES", diff --git a/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts b/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts index c27cb8d50..21cc2bfa1 100644 --- a/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts +++ b/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts @@ -107,7 +107,7 @@ export const folderReducer = createReducer(initialState, { state: FolderReduxState, action: ReduxAction ): FolderReduxState => { - const elements = { ...state.folderElements }; + let elements = { ...state.folderElements }; elements[action.payload.sourceFolderId ?? ""] = elements[ action.payload.sourceFolderId ?? "" ]?.filter( @@ -120,6 +120,44 @@ export const folderReducer = createReducer(initialState, { folderElements: elements, }; }, + [ReduxActionTypes.MOVE_TO_FOLDER2_SUCCESS]: ( + state: FolderReduxState, + action: ReduxAction + ): FolderReduxState => { + let elements = { ...state.folderElements }; + let tempIndex: number | undefined; + let tempNode: any; + let temp = elements[""].map((item, index) => { + if (item.folderId === action.payload.sourceFolderId && item.folder) { + + const tempSubApplications = item.subApplications?.filter(e => + (e.folder && e.folderId !== action.payload.sourceId) || + (!e.folder && e.applicationId !== action.payload.sourceId) + ); + tempNode = item.subApplications?.filter(e => + (e.folder && e.folderId === action.payload.sourceId) || + (!e.folder && e.applicationId === action.payload.sourceId) + ); + return { ...item, subApplications: tempSubApplications }; + } + if (item.folderId === action.payload.folderId && item.folder) { + tempIndex = index; + return item; + } + return item; + }); + if (tempIndex !== undefined) { + const targetItem = temp[tempIndex]; + if (targetItem.folder && Array.isArray(targetItem.subApplications)) { + targetItem.subApplications.push(tempNode[0]); + } + } + elements[""] = temp; + return { + ...state, + folderElements: elements, + }; + }, [ReduxActionTypes.DELETE_FOLDER_SUCCESS]: ( state: FolderReduxState, action: ReduxAction diff --git a/client/packages/lowcoder/src/redux/reduxActions/folderActions.ts b/client/packages/lowcoder/src/redux/reduxActions/folderActions.ts index ba288b89a..5c00aafe6 100644 --- a/client/packages/lowcoder/src/redux/reduxActions/folderActions.ts +++ b/client/packages/lowcoder/src/redux/reduxActions/folderActions.ts @@ -58,6 +58,7 @@ export interface MoveToFolderPayload { sourceFolderId: string; sourceId: string; folderId: string; + moveFlag?: boolean; } export const moveToFolder = ( diff --git a/client/packages/lowcoder/src/redux/sagas/folderSagas.ts b/client/packages/lowcoder/src/redux/sagas/folderSagas.ts index 65a39f030..62b74659e 100644 --- a/client/packages/lowcoder/src/redux/sagas/folderSagas.ts +++ b/client/packages/lowcoder/src/redux/sagas/folderSagas.ts @@ -84,14 +84,16 @@ export function* deleteFolderSaga(action: ReduxActionWithCallbacks) { try { + const { moveFlag } = action.payload; + delete action.payload.moveFlag; const response: AxiosResponse> = yield FolderApi.moveToFolder( action.payload ); const isValidResponse: boolean = validateResponse(response); - + const type = moveFlag ? ReduxActionTypes.MOVE_TO_FOLDER2_SUCCESS : ReduxActionTypes.MOVE_TO_FOLDER_SUCCESS; if (isValidResponse) { yield put({ - type: ReduxActionTypes.MOVE_TO_FOLDER_SUCCESS, + type, payload: action.payload, }); action.onSuccessCallback && action.onSuccessCallback(response); From b6e07d55815abd7eeaebbf8d08dc19a47af7e155 Mon Sep 17 00:00:00 2001 From: Imiss-U1025 Date: Thu, 21 Nov 2024 11:56:59 -0500 Subject: [PATCH 14/36] Made be possible Drag and Drop in extension. --- .../DraggableTree/DraggableItem.tsx | 2 +- .../DraggableTree/DroppableMenuItem.tsx | 52 +++++++++++-------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/client/packages/lowcoder/src/components/DraggableTree/DraggableItem.tsx b/client/packages/lowcoder/src/components/DraggableTree/DraggableItem.tsx index c8a0f093e..4d827381c 100644 --- a/client/packages/lowcoder/src/components/DraggableTree/DraggableItem.tsx +++ b/client/packages/lowcoder/src/components/DraggableTree/DraggableItem.tsx @@ -15,7 +15,7 @@ const Wrapper = styled.div<{ $itemHeight?: number; }>` position: relative; - width: 100%; + width: auto; height: ${(props) => props.$itemHeight ?? 30}px; /* border: 1px solid #d7d9e0; */ border-radius: 4px; diff --git a/client/packages/lowcoder/src/components/DraggableTree/DroppableMenuItem.tsx b/client/packages/lowcoder/src/components/DraggableTree/DroppableMenuItem.tsx index 68c355ec3..3d49e438a 100644 --- a/client/packages/lowcoder/src/components/DraggableTree/DroppableMenuItem.tsx +++ b/client/packages/lowcoder/src/components/DraggableTree/DroppableMenuItem.tsx @@ -6,6 +6,7 @@ import { DraggableTreeContext } from "./DraggableTreeContext"; import DroppablePlaceholder from "./DroppablePlaceHolder"; import { DraggableTreeNode, DraggableTreeNodeItemRenderProps, IDragData, IDropData } from "./types"; import { checkDroppableFlag } from "./util"; +import { Flex } from "antd"; const DraggableMenuItemWrapper = styled.div` position: relative; @@ -88,29 +89,34 @@ export default function DraggableMenuItem(props: IDraggableMenuItemProps) { disabled={isDragging || disabled} /> )} - { - setDragNodeRef(node); - setDropNodeRef(node); - }} - {...dragListeners} - > - {renderContent?.({ - node: item, - isOver, - path, - isOverlay, - hasChildren: items.length > 0, - dragging: !!(isDragging || parentDragging), - isFolded: isFold, - onDelete: () => onDelete?.(path), - onToggleFold: () => context.toggleFold(id), - }) || null} - + + { + setDragNodeRef(node); + setDropNodeRef(node); + }} + {...dragListeners} + > + ⣿ + +
+ {renderContent?.({ + node: item, + isOver, + path, + isOverlay, + hasChildren: items.length > 0, + dragging: !!(isDragging || parentDragging), + isFolded: isFold, + onDelete: () => onDelete?.(path), + onToggleFold: () => context.toggleFold(id), + }) || null} +
+
{items.length > 0 && !isFold && (
From 63534caada12c8fb5f85ee05c97621a8fda03d55 Mon Sep 17 00:00:00 2001 From: Imiss-U1025 Date: Thu, 21 Nov 2024 16:34:11 -0500 Subject: [PATCH 15/36] Added removing module. --- .../src/pages/editor/right/ModulePanel.tsx | 189 ++++++++++++++---- .../reducers/uiReducers/folderReducer.ts | 20 +- 2 files changed, 163 insertions(+), 46 deletions(-) diff --git a/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx b/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx index 7655ad57b..7951a36e3 100644 --- a/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx +++ b/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx @@ -13,13 +13,13 @@ import { PointIcon, PopupCard, UnfoldIcon, - FileFolderIcon + FileFolderIcon, messageInstance, CustomModal } from "lowcoder-design"; -import { trans } from "i18n"; +import {trans, transToNode} from "i18n"; import { draggingUtils } from "layout/draggingUtils"; -import {CSSProperties, useContext, useEffect, useState} from "react"; +import React, {CSSProperties, useContext, useEffect, useState} from "react"; import { useDispatch, useSelector } from "react-redux"; -import { fetchAllModules } from "redux/reduxActions/applicationActions"; +import {fetchAllModules, recycleApplication} from "redux/reduxActions/applicationActions"; import styled from "styled-components"; import CreateAppButton from "components/CreateAppButton"; import { TransparentImg } from "util/commonUtils"; @@ -31,12 +31,20 @@ import {showAppSnapshotSelector} from "@lowcoder-ee/redux/selectors/appSnapshotS import {DraggableTreeNode, DraggableTreeNodeItemRenderProps} from "@lowcoder-ee/components/DraggableTree/types"; import RefTreeComp from "@lowcoder-ee/comps/comps/refTreeComp"; import { EmptyContent } from "components/EmptyContent"; +import {moveToFolder} from "@lowcoder-ee/redux/reduxActions/folderActions"; +import {HomeResInfo} from "@lowcoder-ee/util/homeResUtils"; const ItemWrapper = styled.div` display: flex; flex-direction: row; &:last-child { margin-bottom: 0; } + .module-container { + //width: 70px; + display: flex; + justify-content: space-between; + text-align: left; + } .module-icon { display: flex; @@ -52,6 +60,8 @@ const ItemWrapper = styled.div` overflow: hidden; } .module-name { + //flex-grow: 1; + //margin-right: 8px; line-height: 1.5; font-size: 13px; overflow: hidden; @@ -77,8 +87,8 @@ function buildTree(elementRecord: Record = {}; let rootNode: NodeType = { - name: "", - id: "", + name: "root", + id: "root", isFolder: true, children: [], rename: val => rootNode.name = val, @@ -99,7 +109,7 @@ function buildTree(elementRecord: Record { + rootNode.children.sort((a, b) => { if (a.isFolder && !b.isFolder) { return -1; // a is a isFolder and should come first } else if (!a.isFolder && b.isFolder) { @@ -150,7 +160,7 @@ function buildTree(elementRecord: Record { console.log(meta); + e.stopPropagation(); e.dataTransfer.setData("compType", compType); e.dataTransfer.setDragImage(TransparentImg, 0, 0); draggingUtils.setData("compType", compType); @@ -183,11 +194,13 @@ function ModuleItem(props: ModuleItemProps) { props.onDrag(compType); }} > -
- -
-
-
{props.meta.name}
+
+
+ +
+
+
{props.meta.name}
+
); @@ -372,28 +385,29 @@ function ModuleSidebarItem(props: ModuleSidebarItemProps) { {isFolder && {!isFolded ? : }} - {isFolder ? - <> - -
- setEditing(editing)} - /> - -
: - } + { isFolder ? + <> + +
+ setEditing(editing)} + /> + +
+ : + } {!readOnly && !isOverlay && ( - + onDelete()}> )} @@ -404,9 +418,10 @@ function ModuleSidebarItem(props: ModuleSidebarItemProps) { export default function ModulePanel() { const dispatch = useDispatch(); - const elements = useSelector(folderElementsSelector); + let elements = useSelector(folderElementsSelector); + // const reload = () => elements = useSelector(folderElementsSelector); const { onDrag, searchValue } = useContext(RightContext); - + const [deleteFlag, setDeleteFlag] = useState(false); useEffect(() => { dispatch(fetchAllModules({})); }, [dispatch]); @@ -433,9 +448,12 @@ export default function ModulePanel() { } const getById = (id: string): NodeType | undefined => getByIdFromNode(tree, id); + let popedItem : DraggableTreeNode[] = []; + let popedItemSourceId = "" const convertRefTree = (treeNode: NodeType) => { const moduleResComp = getById(treeNode.id); const currentNodeType = moduleResComp?.isFolder; + const childrenItems = treeNode.children .map((i) => convertRefTree(i as NodeType)) .filter((i): i is DraggableTreeNode => !!i); @@ -473,21 +491,73 @@ export default function ModulePanel() { data: moduleResComp, addSubItem(value) { console.log("addSubItem", node.id, value, node); + // node.items.push(value) // const pushAction = node.items.pushAction({ value: value.id() }); // node.items.dispatch(pushAction); }, deleteItem(index) { - console.log("deleteItem", node.id, index); + console.log("deleteItem", node, index); + popedItemSourceId = node.id!; + if(!deleteFlag){ + popedItem = node.items.splice(index, 1); + console.log(popedItem); + } + // const deleteAction = node.children.items.deleteAction(index); // node.children.items.dispatch(deleteAction); }, addItem(value) { - console.log("addItem", node.id, value); + console.log("additem", "value", value, node); + node.items.push(popedItem[0]) + popedItem = []; // const pushAction = node.children.items.pushAction({ value: value.id() }); // node.children.items.dispatch(pushAction); + // if (popedItem[0]){ + // dispatch( + // moveToFolder( + // { + // sourceFolderId: popedItemSourceId, + // sourceId: popedItem[0].id!, + // folderId: node.id!, + // moveFlag: true + // }, + // () => { + // + // + // }, + // () => {} + // ) + // ); + // node.items.push(popedItem[0]); + // popedItemSourceId = ""; + // popedItem = []; + // } }, moveItem(from, to) { - console.log("node", node); + console.log("moveItem", node, from, to, node.id); + if (popedItem[0]){ + node.items.push(popedItem[0]); + + dispatch( + moveToFolder( + { + sourceFolderId: popedItemSourceId, + sourceId: popedItem[0].id!, + folderId: node.id!, + moveFlag: true + }, + () => { + + + }, + () => {} + ) + ); + popedItemSourceId = ""; + popedItem = []; + + } + // popedItem = []; // const moveAction = node.children.items.arrayMoveAction(from, to); // node.children.items.dispatch(moveAction); }, @@ -505,18 +575,51 @@ export default function ModulePanel() { }; const node = convertRefTree(tree); - + console.log("started!!!!", node) function onCopy(type: boolean, id: string) { console.log("onCopy", type, id); } function onSelect(type: boolean, id: string, meta: any) { console.log("onSelect", type, id, meta) - return + // return } function onDelete(type: boolean, id: string) { - console.log("onDelete", type, id); + setDeleteFlag(true); + console.log("1111111111111111111111111", type, id, node); + if (type) { + alert(1); + } + else { + CustomModal.confirm({ + title: trans("home.moveToTrash"), + content: transToNode("home.moveToTrashSubTitle", { + type: "", + name: "This file", + }), + onConfirm: () => { + dispatch( + recycleApplication( + { + applicationId: id, + folderId: popedItemSourceId, + }, + () => { + messageInstance.success(trans("success")) + + }, + () => { + } + ) + ) + setDeleteFlag(false) + }, + confirmBtnType: "delete", + okText: trans("home.moveToTrash"), + onCancel: () => setDeleteFlag(false) + }); + } return true; } diff --git a/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts b/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts index 21cc2bfa1..4d1562811 100644 --- a/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts +++ b/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts @@ -37,10 +37,24 @@ export const folderReducer = createReducer(initialState, { state: FolderReduxState, action: ReduxAction ): FolderReduxState => { + const deleteArray : number[] = []; const elements = { ...state.folderElements }; - elements[action.payload.folderId ?? ""] = elements[action.payload.folderId ?? ""]?.filter( - (e) => e.folder || (!e.folder && e.applicationId !== action.payload.applicationId) - ); + elements[""] = elements[""].map((item, index) => { + if(item.folder) { + const tempSubApplications = item.subApplications?.filter(e => e.applicationId !== action.payload.applicationId); + return { ...item, subApplications: tempSubApplications }; + } else { + if (item.applicationId !== action.payload.applicationId) + return item; + else { + deleteArray.push(index); + return item; + } + } + }); + deleteArray.map(item => { + elements[""].splice(item, 1); + }) return { ...state, folderElements: elements, From c8daa8dd36b84ca17efd20a2e8ddcb52362822a1 Mon Sep 17 00:00:00 2001 From: Imiss-U1025 Date: Fri, 22 Nov 2024 01:16:35 -0500 Subject: [PATCH 16/36] Added removing folder. --- .../packages/lowcoder/src/i18n/locales/en.ts | 1 + .../src/pages/editor/right/ModulePanel.tsx | 81 ++++++++++++------- 2 files changed, 52 insertions(+), 30 deletions(-) diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index a4672903e..999890d01 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -2785,6 +2785,7 @@ export const en = { "switch": "Switch Component: " }, "module": { + "folderNotEmpty": "Folder is not empty", "emptyText": "No Data", "docLink": "Read More About Modules...", "documentationText" : "Modules are complete Applications, that can get included and repeated in other Applications and it functions just like a single component. As modules can get embedded, they need to be able to interact with your outside apps or websites. This four settings help to support communication with a Module.", diff --git a/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx b/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx index 7951a36e3..07e3a91a4 100644 --- a/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx +++ b/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx @@ -31,7 +31,7 @@ import {showAppSnapshotSelector} from "@lowcoder-ee/redux/selectors/appSnapshotS import {DraggableTreeNode, DraggableTreeNodeItemRenderProps} from "@lowcoder-ee/components/DraggableTree/types"; import RefTreeComp from "@lowcoder-ee/comps/comps/refTreeComp"; import { EmptyContent } from "components/EmptyContent"; -import {moveToFolder} from "@lowcoder-ee/redux/reduxActions/folderActions"; +import {deleteFolder, moveToFolder} from "@lowcoder-ee/redux/reduxActions/folderActions"; import {HomeResInfo} from "@lowcoder-ee/util/homeResUtils"; const ItemWrapper = styled.div` display: flex; @@ -585,42 +585,65 @@ export default function ModulePanel() { // return } - function onDelete(type: boolean, id: string) { + function onDelete(type: boolean, id: string, node: NodeType) { setDeleteFlag(true); console.log("1111111111111111111111111", type, id, node); if (type) { - alert(1); - } - else { - CustomModal.confirm({ - title: trans("home.moveToTrash"), - content: transToNode("home.moveToTrashSubTitle", { - type: "", - name: "This file", - }), - onConfirm: () => { + if (node.children.length) { + messageInstance.error(trans("module.folderNotEmpty")) + } else { + try { dispatch( - recycleApplication( - { - applicationId: id, - folderId: popedItemSourceId, - }, + deleteFolder( + {folderId: id, parentFolderId: ""}, () => { - messageInstance.success(trans("success")) - + messageInstance.success(trans("home.deleteSuccessMsg")); }, () => { + messageInstance.error(trans("error")) } ) - ) - setDeleteFlag(false) - }, - confirmBtnType: "delete", - okText: trans("home.moveToTrash"), - onCancel: () => setDeleteFlag(false) - }); + ); + } catch (error) { + console.error("Error: Delete module in extension:", error); + throw error; + } + } + } else { + try { + CustomModal.confirm({ + title: trans("home.moveToTrash"), + content: transToNode("home.moveToTrashSubTitle", { + type: "", + name: "This file", + }), + onConfirm: () => { + dispatch( + recycleApplication( + { + applicationId: id, + folderId: popedItemSourceId, + }, + () => { + messageInstance.success(trans("success")); + + }, + () => { + messageInstance.error(trans("error")); + } + ) + ) + setDeleteFlag(false) + }, + confirmBtnType: "delete", + okText: trans("home.moveToTrash"), + onCancel: () => setDeleteFlag(false) + }); + } catch (error) { + console.error("Error: Delete module in extension:", error); + throw error; + } } - return true; } return ( @@ -661,9 +684,7 @@ export default function ModulePanel() { onCopy={() => onCopy(isFolder, id)} onSelect={() => onSelect(isFolder, id, resComp)} onDelete={() => { - if (onDelete(isFolder, id)) { - onDeleteTreeItem(); - } + (onDelete(isFolder, id, resComp)) }} {...otherParams} /> From 39dbd40d966692f3a08219227a9e66d865616d76 Mon Sep 17 00:00:00 2001 From: Imiss-U1025 Date: Fri, 22 Nov 2024 04:19:43 -0500 Subject: [PATCH 17/36] Added Renaming folders. --- .../src/pages/editor/right/ModulePanel.tsx | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx b/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx index 07e3a91a4..bf66584cb 100644 --- a/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx +++ b/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx @@ -31,7 +31,7 @@ import {showAppSnapshotSelector} from "@lowcoder-ee/redux/selectors/appSnapshotS import {DraggableTreeNode, DraggableTreeNodeItemRenderProps} from "@lowcoder-ee/components/DraggableTree/types"; import RefTreeComp from "@lowcoder-ee/comps/comps/refTreeComp"; import { EmptyContent } from "components/EmptyContent"; -import {deleteFolder, moveToFolder} from "@lowcoder-ee/redux/reduxActions/folderActions"; +import {deleteFolder, moveToFolder, updateFolder} from "@lowcoder-ee/redux/reduxActions/folderActions"; import {HomeResInfo} from "@lowcoder-ee/util/homeResUtils"; const ItemWrapper = styled.div` display: flex; @@ -300,6 +300,10 @@ interface ModuleSidebarItemProps extends DraggableTreeNodeItemRenderProps { onSelect: () => void; onDelete: () => void; onToggleFold: () => void; + selectedID: string; + setSelectedID: (id: string) => void; + selectedType: boolean; + setSelectedType: (id: boolean) => void; } const empty = ( @@ -321,6 +325,7 @@ const empty = ( ); function ModuleSidebarItem(props: ModuleSidebarItemProps) { + const dispatch = useDispatch(); const { id, resComp, @@ -328,6 +333,10 @@ function ModuleSidebarItem(props: ModuleSidebarItemProps) { isOverlay, path, isFolded, + selectedID, + setSelectedID, + selectedType, + setSelectedType, onDelete, onCopy, onSelect, @@ -338,13 +347,11 @@ function ModuleSidebarItem(props: ModuleSidebarItemProps) { const [editing, setEditing] = useState(false); const editorState = useContext(EditorContext); const readOnly = useSelector(showAppSnapshotSelector); - const [selectedModuleResName, setSelectedModuleResName] = useState(""); - const [selectedModuleResType, setSelectedModuleResType] = useState(false); const level = path.length - 1; const type = resComp.isFolder; const name = resComp.name; const icon = resComp.isFolder? : ; - const isSelected = type === selectedModuleResType && id === selectedModuleResName; + const isSelected = type === selectedType && id === selectedID; const isFolder = type; const handleFinishRename = (value: string) => { @@ -358,20 +365,15 @@ function ModuleSidebarItem(props: ModuleSidebarItemProps) { success = true; } if (success) { - setSelectedModuleResName(compId); - setSelectedModuleResType(type); + setSelectedID(compId); + setSelectedType(type); setError(undefined); + dispatch(updateFolder({ id: selectedID, name: value })); } }; const handleNameChange = (value: string) => { - let err = ""; - if (resComp.checkName) { - err = resComp.checkName(value); - } else { - err = editorState.checkRename(name, value); - } - setError(err); + value === "" ? setError("Cannot Be Empty") : setError(""); }; const handleClickItem = () => { @@ -422,6 +424,8 @@ export default function ModulePanel() { // const reload = () => elements = useSelector(folderElementsSelector); const { onDrag, searchValue } = useContext(RightContext); const [deleteFlag, setDeleteFlag] = useState(false); + const [selectedID, setSelectedID] = useState(""); + const [selectedType, setSelectedType] = useState(false); useEffect(() => { dispatch(fetchAllModules({})); }, [dispatch]); @@ -575,14 +579,14 @@ export default function ModulePanel() { }; const node = convertRefTree(tree); - console.log("started!!!!", node) function onCopy(type: boolean, id: string) { console.log("onCopy", type, id); } function onSelect(type: boolean, id: string, meta: any) { + setSelectedID(id); + setSelectedType(type); console.log("onSelect", type, id, meta) - // return } function onDelete(type: boolean, id: string, node: NodeType) { @@ -683,6 +687,10 @@ export default function ModulePanel() { onToggleFold={onToggleFold} onCopy={() => onCopy(isFolder, id)} onSelect={() => onSelect(isFolder, id, resComp)} + selectedID={selectedID} + setSelectedID={setSelectedID} + selectedType={selectedType} + setSelectedType={setSelectedType} onDelete={() => { (onDelete(isFolder, id, resComp)) }} From 521370f0e6cc65536c490cba935cd5db317592d0 Mon Sep 17 00:00:00 2001 From: Imiss-U1025 Date: Fri, 22 Nov 2024 09:06:27 -0500 Subject: [PATCH 18/36] Added removing modules --- .../src/pages/editor/right/ModulePanel.tsx | 99 +++++++++++++++++-- .../reducers/uiReducers/folderReducer.ts | 9 ++ 2 files changed, 100 insertions(+), 8 deletions(-) diff --git a/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx b/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx index bf66584cb..a5ef3f989 100644 --- a/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx +++ b/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx @@ -19,7 +19,7 @@ import {trans, transToNode} from "i18n"; import { draggingUtils } from "layout/draggingUtils"; import React, {CSSProperties, useContext, useEffect, useState} from "react"; import { useDispatch, useSelector } from "react-redux"; -import {fetchAllModules, recycleApplication} from "redux/reduxActions/applicationActions"; +import {fetchAllModules, recycleApplication, updateAppMetaAction} from "redux/reduxActions/applicationActions"; import styled from "styled-components"; import CreateAppButton from "components/CreateAppButton"; import { TransparentImg } from "util/commonUtils"; @@ -27,7 +27,7 @@ import { ComListTitle } from "./styledComponent"; import {folderElementsSelector} from "@lowcoder-ee/redux/selectors/folderSelector"; import {DraggableTree} from "@lowcoder-ee/components/DraggableTree/DraggableTree"; import {EditorContext} from "lowcoder-sdk"; -import {showAppSnapshotSelector} from "@lowcoder-ee/redux/selectors/appSnapshotSelector"; +import {getSelectedAppSnapshot, showAppSnapshotSelector} from "@lowcoder-ee/redux/selectors/appSnapshotSelector"; import {DraggableTreeNode, DraggableTreeNodeItemRenderProps} from "@lowcoder-ee/components/DraggableTree/types"; import RefTreeComp from "@lowcoder-ee/comps/comps/refTreeComp"; import { EmptyContent } from "components/EmptyContent"; @@ -168,11 +168,65 @@ function buildTree(elementRecord: Record void; + isOverlay: boolean; + selectedID: string; + setSelectedID: (id: string) => void; + selectedType: boolean; + setSelectedType: (id: boolean) => void; + resComp: NodeType; + id: string; } function ModuleItem(props: ModuleItemProps) { const compType = "module"; - const { meta } = props; + const { + meta , + isOverlay, + selectedID, + setSelectedID, + selectedType, + setSelectedType, + resComp, + id + } = props; + const dispatch = useDispatch(); + const type = resComp.isFolder; + const name = resComp.name; + const [error, setError] = useState(undefined); + const [editing, setEditing] = useState(false); + const editorState = useContext(EditorContext); + const readOnly = useSelector(showAppSnapshotSelector); + const isSelected = type === selectedType && id === selectedID; + const handleFinishRename = (value: string) => { + let success = false; + let compId = name; + if (resComp.rename) { + compId = resComp.rename(value); + success = !!compId; + } else { + compId = name; + success = true; + } + if (success) { + console.log(selectedID, value); + setSelectedID(compId); + setSelectedType(type); + setError(undefined); + try{ + dispatch(updateAppMetaAction({ + applicationId: selectedID, + name: value + })); + } catch (error) { + console.error("Error: Delete module in extension:", error); + throw error; + } + } + }; + + const handleNameChange = (value: string) => { + value === "" ? setError("Cannot Be Empty") : setError(""); + }; return (
-
-
{props.meta.name}
+ +
+ setEditing(editing)} + /> +
@@ -368,7 +436,13 @@ function ModuleSidebarItem(props: ModuleSidebarItemProps) { setSelectedID(compId); setSelectedType(type); setError(undefined); - dispatch(updateFolder({ id: selectedID, name: value })); + try{ + dispatch(updateFolder({ id: selectedID, name: value })); + } catch (error) { + console.error("Error: Delete module in extension:", error); + throw error; + } + } }; @@ -407,7 +481,17 @@ function ModuleSidebarItem(props: ModuleSidebarItemProps) { />
: - } + } {!readOnly && !isOverlay && ( onDelete()}> @@ -421,7 +505,6 @@ function ModuleSidebarItem(props: ModuleSidebarItemProps) { export default function ModulePanel() { const dispatch = useDispatch(); let elements = useSelector(folderElementsSelector); - // const reload = () => elements = useSelector(folderElementsSelector); const { onDrag, searchValue } = useContext(RightContext); const [deleteFlag, setDeleteFlag] = useState(false); const [selectedID, setSelectedID] = useState(""); diff --git a/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts b/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts index 4d1562811..4ad02e446 100644 --- a/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts +++ b/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts @@ -69,6 +69,15 @@ export const folderReducer = createReducer(initialState, { elements[action.payload.folderId ?? ""] = elements[action.payload.folderId ?? ""]?.map((e) => { if (!e.folder && e.applicationId === action.payload.applicationId) { return { ...e, ...action.payload }; + } else { + if (e.folder) { + // console.log(e.subApplications); + if (e.subApplications?.map(item => { + if (item.applicationId === action.payload.applicationId) + item.name = action.payload.name + })){ + } + } } return e; }); From fdc01456321016875d70a486b006435ba91fd28e Mon Sep 17 00:00:00 2001 From: Imiss-U1025 Date: Fri, 22 Nov 2024 09:46:41 -0500 Subject: [PATCH 19/36] Fixed UI. --- .../src/pages/editor/right/ModulePanel.tsx | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx b/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx index a5ef3f989..5c7dea317 100644 --- a/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx +++ b/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx @@ -40,17 +40,12 @@ const ItemWrapper = styled.div` margin-bottom: 0; } .module-container { - //width: 70px; display: flex; - justify-content: space-between; - text-align: left; } .module-icon { - - display: flex; - justify-content: center; - align-items: center; - margin: 4px; + margin-right: 4px; + width:19px; + height: 19px; } .module-content { flex: 1; @@ -249,10 +244,7 @@ function ModuleItem(props: ModuleItemProps) { }} >
-
- -
- +
(props.$active ? BorderActiveColor : "transparent")}; align-items: center; - justify-content: center; + justify-content: space-between; `; interface ColumnDivProps { @@ -463,7 +455,7 @@ function ModuleSidebarItem(props: ModuleSidebarItemProps) { {isFolder && {!isFolded ? : }} { isFolder ? <> - +
Date: Fri, 22 Nov 2024 11:57:01 -0500 Subject: [PATCH 20/36] Fxied ability to module drag and drop in right panel. --- .../src/pages/editor/right/ModulePanel.tsx | 126 ++++++++---------- .../reducers/uiReducers/folderReducer.ts | 66 +++++---- 2 files changed, 94 insertions(+), 98 deletions(-) diff --git a/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx b/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx index 5c7dea317..7510bad67 100644 --- a/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx +++ b/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx @@ -83,7 +83,7 @@ function buildTree(elementRecord: Record = {}; let rootNode: NodeType = { name: "root", - id: "root", + id: "", isFolder: true, children: [], rename: val => rootNode.name = val, @@ -498,16 +498,54 @@ export default function ModulePanel() { const dispatch = useDispatch(); let elements = useSelector(folderElementsSelector); const { onDrag, searchValue } = useContext(RightContext); - const [deleteFlag, setDeleteFlag] = useState(false); const [selectedID, setSelectedID] = useState(""); const [selectedType, setSelectedType] = useState(false); + let sourceFolderId : string = ""; + let sourceId : string = ""; + let folderId : string = ""; + const tree = buildTree(elements); + const getById = (id: string): NodeType | undefined => getByIdFromNode(tree, id); + let popedItem : DraggableTreeNode[] = []; + let popedItemSourceId = ""; + useEffect(() => { - dispatch(fetchAllModules({})); + dispatch(fetchAllModules({})); }, [dispatch]); + const moveModule = () => { + console.log({sourceFolderId: sourceFolderId, + sourceId: sourceId, + folderId: folderId, + moveFlag: true}) + try{ + if (sourceId !== "") { + dispatch( + moveToFolder( + { + sourceFolderId: sourceFolderId!, + sourceId: sourceId!, + folderId: folderId!, + moveFlag: true + }, + () => { + + + }, + () => {} + ) + ); + } + } catch (error) { + console.error("Error: Delete module in extension:", error); + throw error; + } finally { + folderId = ""; + sourceId = ""; + sourceFolderId = ""; + } + + } - //Convert elements into tree - const tree = buildTree(elements); const getByIdFromNode = (root: NodeType | null, id: string): NodeType | undefined => { if (!root) { return; @@ -525,11 +563,7 @@ export default function ModulePanel() { } return; } - - const getById = (id: string): NodeType | undefined => getByIdFromNode(tree, id); - let popedItem : DraggableTreeNode[] = []; - let popedItemSourceId = "" - const convertRefTree = (treeNode: NodeType) => { + const convertRefTree = (treeNode: NodeType) => { //Convert elements into tree const moduleResComp = getById(treeNode.id); const currentNodeType = moduleResComp?.isFolder; @@ -570,75 +604,25 @@ export default function ModulePanel() { data: moduleResComp, addSubItem(value) { console.log("addSubItem", node.id, value, node); + folderId = node.id!; + moveModule(); // node.items.push(value) // const pushAction = node.items.pushAction({ value: value.id() }); // node.items.dispatch(pushAction); }, deleteItem(index) { - console.log("deleteItem", node, index); - popedItemSourceId = node.id!; - if(!deleteFlag){ - popedItem = node.items.splice(index, 1); - console.log(popedItem); - } + console.log("deleteItem", index, node); + sourceFolderId = node.id!; + sourceId = node.items[index].id!; - // const deleteAction = node.children.items.deleteAction(index); - // node.children.items.dispatch(deleteAction); }, addItem(value) { - console.log("additem", "value", value, node); - node.items.push(popedItem[0]) - popedItem = []; - // const pushAction = node.children.items.pushAction({ value: value.id() }); - // node.children.items.dispatch(pushAction); - // if (popedItem[0]){ - // dispatch( - // moveToFolder( - // { - // sourceFolderId: popedItemSourceId, - // sourceId: popedItem[0].id!, - // folderId: node.id!, - // moveFlag: true - // }, - // () => { - // - // - // }, - // () => {} - // ) - // ); - // node.items.push(popedItem[0]); - // popedItemSourceId = ""; - // popedItem = []; - // } + console.log("additem", "value", value, "node", node); + folderId = node.id!; + moveModule(); }, moveItem(from, to) { console.log("moveItem", node, from, to, node.id); - if (popedItem[0]){ - node.items.push(popedItem[0]); - - dispatch( - moveToFolder( - { - sourceFolderId: popedItemSourceId, - sourceId: popedItem[0].id!, - folderId: node.id!, - moveFlag: true - }, - () => { - - - }, - () => {} - ) - ); - popedItemSourceId = ""; - popedItem = []; - - } - // popedItem = []; - // const moveAction = node.children.items.arrayMoveAction(from, to); - // node.children.items.dispatch(moveAction); }, }; @@ -652,7 +636,6 @@ export default function ModulePanel() { } return node; }; - const node = convertRefTree(tree); function onCopy(type: boolean, id: string) { console.log("onCopy", type, id); @@ -665,8 +648,8 @@ export default function ModulePanel() { } function onDelete(type: boolean, id: string, node: NodeType) { - setDeleteFlag(true); console.log("1111111111111111111111111", type, id, node); + if (type) { if (node.children.length) { messageInstance.error(trans("module.folderNotEmpty")) @@ -712,11 +695,10 @@ export default function ModulePanel() { } ) ) - setDeleteFlag(false) }, confirmBtnType: "delete", okText: trans("home.moveToTrash"), - onCancel: () => setDeleteFlag(false) + onCancel: () => {} }); } catch (error) { console.error("Error: Delete module in extension:", error); diff --git a/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts b/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts index 4ad02e446..e4ca19920 100644 --- a/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts +++ b/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts @@ -71,7 +71,6 @@ export const folderReducer = createReducer(initialState, { return { ...e, ...action.payload }; } else { if (e.folder) { - // console.log(e.subApplications); if (e.subApplications?.map(item => { if (item.applicationId === action.payload.applicationId) item.name = action.payload.name @@ -148,34 +147,49 @@ export const folderReducer = createReducer(initialState, { action: ReduxAction ): FolderReduxState => { let elements = { ...state.folderElements }; - let tempIndex: number | undefined; - let tempNode: any; - let temp = elements[""].map((item, index) => { - if (item.folderId === action.payload.sourceFolderId && item.folder) { - - const tempSubApplications = item.subApplications?.filter(e => - (e.folder && e.folderId !== action.payload.sourceId) || - (!e.folder && e.applicationId !== action.payload.sourceId) - ); - tempNode = item.subApplications?.filter(e => - (e.folder && e.folderId === action.payload.sourceId) || - (!e.folder && e.applicationId === action.payload.sourceId) - ); - return { ...item, subApplications: tempSubApplications }; - } - if (item.folderId === action.payload.folderId && item.folder) { - tempIndex = index; + const { sourceId, folderId, sourceFolderId } = action.payload; + if(sourceFolderId === "") { + const tempItem = elements[""]?.find(e => + !e.folder && e.applicationId === sourceId + ); + elements[""] = elements[""]?.filter(e => e.folder || (e.applicationId !== sourceId)); + elements[""] = elements[""].map(item => { + if(item.folder && item.folderId === folderId && tempItem !== undefined && !tempItem.folder) { + item.subApplications?.push(tempItem); + } return item; + }) + } else{ + let tempIndex: number | undefined; + let tempNode: any; + let temp = elements[""].map((item, index) => { + if (item.folderId === sourceFolderId && item.folder) { + const tempSubApplications = item.subApplications?.filter(e => + (e.folder && e.folderId !== sourceId) || + (!e.folder && e.applicationId !== sourceId) + ); + tempNode = item.subApplications?.filter(e => + (e.folder && e.folderId === sourceId) || + (!e.folder && e.applicationId === sourceId) + ); + return { ...item, subApplications: tempSubApplications }; + } + if (item.folderId === folderId && item.folder) { + tempIndex = index; + return item; + } + return item; + }); + if (tempIndex !== undefined) { + const targetItem = temp[tempIndex]; + if (targetItem.folder && Array.isArray(targetItem.subApplications)) { + targetItem.subApplications.push(tempNode[0]); + } + } else { + temp.push(tempNode[0]); } - return item; - }); - if (tempIndex !== undefined) { - const targetItem = temp[tempIndex]; - if (targetItem.folder && Array.isArray(targetItem.subApplications)) { - targetItem.subApplications.push(tempNode[0]); - } + elements[""] = temp; } - elements[""] = temp; return { ...state, folderElements: elements, From 4e4e9064035e4b99ba767bd32203b1980b417f23 Mon Sep 17 00:00:00 2001 From: Imiss-U1025 Date: Fri, 22 Nov 2024 14:52:07 -0500 Subject: [PATCH 21/36] Fixed an issue where subApplications are hidden when rename a folder. --- .../src/pages/editor/right/ModulePanel.tsx | 396 +++++++++--------- .../reducers/uiReducers/folderReducer.ts | 2 +- 2 files changed, 189 insertions(+), 209 deletions(-) diff --git a/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx b/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx index 7510bad67..3110647a4 100644 --- a/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx +++ b/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx @@ -17,7 +17,7 @@ import { } from "lowcoder-design"; import {trans, transToNode} from "i18n"; import { draggingUtils } from "layout/draggingUtils"; -import React, {CSSProperties, useContext, useEffect, useState} from "react"; +import React, { useContext, useEffect, useState} from "react"; import { useDispatch, useSelector } from "react-redux"; import {fetchAllModules, recycleApplication, updateAppMetaAction} from "redux/reduxActions/applicationActions"; import styled from "styled-components"; @@ -26,13 +26,10 @@ import { TransparentImg } from "util/commonUtils"; import { ComListTitle } from "./styledComponent"; import {folderElementsSelector} from "@lowcoder-ee/redux/selectors/folderSelector"; import {DraggableTree} from "@lowcoder-ee/components/DraggableTree/DraggableTree"; -import {EditorContext} from "lowcoder-sdk"; -import {getSelectedAppSnapshot, showAppSnapshotSelector} from "@lowcoder-ee/redux/selectors/appSnapshotSelector"; +import { showAppSnapshotSelector} from "@lowcoder-ee/redux/selectors/appSnapshotSelector"; import {DraggableTreeNode, DraggableTreeNodeItemRenderProps} from "@lowcoder-ee/components/DraggableTree/types"; -import RefTreeComp from "@lowcoder-ee/comps/comps/refTreeComp"; import { EmptyContent } from "components/EmptyContent"; import {deleteFolder, moveToFolder, updateFolder} from "@lowcoder-ee/redux/reduxActions/folderActions"; -import {HomeResInfo} from "@lowcoder-ee/util/homeResUtils"; const ItemWrapper = styled.div` display: flex; flex-direction: row; @@ -161,15 +158,15 @@ function buildTree(elementRecord: Record void; - isOverlay: boolean; - selectedID: string; - setSelectedID: (id: string) => void; - selectedType: boolean; - setSelectedType: (id: boolean) => void; - resComp: NodeType; - id: string; + meta: ApplicationMeta; + onDrag: (type: string) => void; + isOverlay: boolean; + selectedID: string; + setSelectedID: (id: string) => void; + selectedType: boolean; + setSelectedType: (id: boolean) => void; + resComp: NodeType; + id: string; } function ModuleItem(props: ModuleItemProps) { @@ -189,44 +186,45 @@ function ModuleItem(props: ModuleItemProps) { const name = resComp.name; const [error, setError] = useState(undefined); const [editing, setEditing] = useState(false); - const editorState = useContext(EditorContext); const readOnly = useSelector(showAppSnapshotSelector); const isSelected = type === selectedType && id === selectedID; const handleFinishRename = (value: string) => { - let success = false; - let compId = name; - if (resComp.rename) { - compId = resComp.rename(value); - success = !!compId; - } else { - compId = name; - success = true; - } - if (success) { - console.log(selectedID, value); - setSelectedID(compId); - setSelectedType(type); - setError(undefined); - try{ - dispatch(updateAppMetaAction({ - applicationId: selectedID, - name: value - })); - } catch (error) { - console.error("Error: Delete module in extension:", error); - throw error; + if (value !== "") { + let success = false; + let compId = name; + if (resComp.rename) { + compId = resComp.rename(value); + success = !!compId; + } else { + compId = name; + success = true; } + if (success) { + setSelectedID(compId); + setSelectedType(type); + setError(undefined); + try { + dispatch(updateAppMetaAction({ + applicationId: selectedID, + name: value + })); + } catch (error) { + console.error("Error: Rename module in extension:", error); + throw error; + } + } + setError(undefined); } + setError(undefined); }; const handleNameChange = (value: string) => { - value === "" ? setError("Cannot Be Empty") : setError(""); + value === "" ? setError("Cannot Be Empty") : setError(undefined); }; return ( { - console.log(meta); e.stopPropagation(); e.dataTransfer.setData("compType", compType); e.dataTransfer.setDragImage(TransparentImg, 0, 0); @@ -244,7 +242,7 @@ function ModuleItem(props: ModuleItemProps) { }} >
- +
` user-select: none; padding-left: 2px; padding-right: 15px; - /* background-color: #ffffff; */ - /* margin: 2px 0; */ background-color: ${(props) => (props.$isOverlay ? "rgba(255, 255, 255, 0.11)" : "")}; &&& { @@ -405,41 +401,42 @@ function ModuleSidebarItem(props: ModuleSidebarItemProps) { const { onDrag } = useContext(RightContext); const [error, setError] = useState(undefined); const [editing, setEditing] = useState(false); - const editorState = useContext(EditorContext); const readOnly = useSelector(showAppSnapshotSelector); const level = path.length - 1; const type = resComp.isFolder; const name = resComp.name; - const icon = resComp.isFolder? : ; const isSelected = type === selectedType && id === selectedID; const isFolder = type; const handleFinishRename = (value: string) => { - let success = false; - let compId = name; - if (resComp.rename) { - compId = resComp.rename(value); - success = !!compId; - } else { - compId = name; - success = true; - } - if (success) { - setSelectedID(compId); - setSelectedType(type); - setError(undefined); - try{ - dispatch(updateFolder({ id: selectedID, name: value })); - } catch (error) { - console.error("Error: Delete module in extension:", error); - throw error; + if (value !== ""){ + let success = false; + let compId = name; + if (resComp.rename) { + compId = resComp.rename(value); + success = !!compId; + } else { + compId = name; + success = true; } + if (success) { + setSelectedID(compId); + setSelectedType(type); + setError(undefined); + try{ + dispatch(updateFolder({ id: selectedID, name: value })); + } catch (error) { + console.error("Error: Delete module in extension:", error); + throw error; + } + } + setError(undefined); } }; const handleNameChange = (value: string) => { - value === "" ? setError("Cannot Be Empty") : setError(""); + value === "" ? setError("Cannot Be Empty") : setError(undefined); }; const handleClickItem = () => { @@ -453,39 +450,39 @@ function ModuleSidebarItem(props: ModuleSidebarItemProps) { {isFolder && {!isFolded ? : }} - { isFolder ? - <> - -
- setEditing(editing)} - /> - -
- : - } + { isFolder ? + <> + +
+ setEditing(editing)} + /> + +
+ : + } {!readOnly && !isOverlay && ( - onDelete()}> + onDelete()}> )} @@ -495,56 +492,51 @@ function ModuleSidebarItem(props: ModuleSidebarItemProps) { } export default function ModulePanel() { - const dispatch = useDispatch(); - let elements = useSelector(folderElementsSelector); - const { onDrag, searchValue } = useContext(RightContext); - const [selectedID, setSelectedID] = useState(""); - const [selectedType, setSelectedType] = useState(false); - let sourceFolderId : string = ""; - let sourceId : string = ""; - let folderId : string = ""; - const tree = buildTree(elements); - const getById = (id: string): NodeType | undefined => getByIdFromNode(tree, id); - let popedItem : DraggableTreeNode[] = []; - let popedItemSourceId = ""; - - useEffect(() => { - dispatch(fetchAllModules({})); - }, [dispatch]); - - const moveModule = () => { - console.log({sourceFolderId: sourceFolderId, - sourceId: sourceId, - folderId: folderId, - moveFlag: true}) - try{ - if (sourceId !== "") { - dispatch( - moveToFolder( - { - sourceFolderId: sourceFolderId!, - sourceId: sourceId!, - folderId: folderId!, - moveFlag: true - }, - () => { - - - }, - () => {} - ) - ); - } - } catch (error) { - console.error("Error: Delete module in extension:", error); - throw error; - } finally { - folderId = ""; - sourceId = ""; - sourceFolderId = ""; - } + const dispatch = useDispatch(); + let elements = useSelector(folderElementsSelector); + const { searchValue } = useContext(RightContext); + const [selectedID, setSelectedID] = useState(""); + const [selectedType, setSelectedType] = useState(false); + let sourceFolderId : string = ""; + let sourceId : string = ""; + let folderId : string = ""; + const tree = buildTree(elements); + const getById = (id: string): NodeType | undefined => getByIdFromNode(tree, id); + let popedItemSourceId = ""; + + useEffect(() => { + dispatch(fetchAllModules({})); + }, [dispatch]); + + const moveModule = () => { + try{ + if (sourceId !== "") { + dispatch( + moveToFolder( + { + sourceFolderId: sourceFolderId!, + sourceId: sourceId!, + folderId: folderId!, + moveFlag: true + }, + () => { + + + }, + () => {} + ) + ); + } + } catch (error) { + console.error("Error: Move module in extension:", error); + throw error; + } finally { + folderId = ""; + sourceId = ""; + sourceFolderId = ""; + } - } + } const getByIdFromNode = (root: NodeType | null, id: string): NodeType | undefined => { if (!root) { @@ -603,26 +595,19 @@ export default function ModulePanel() { items: childrenItems, data: moduleResComp, addSubItem(value) { - console.log("addSubItem", node.id, value, node); folderId = node.id!; moveModule(); - // node.items.push(value) - // const pushAction = node.items.pushAction({ value: value.id() }); - // node.items.dispatch(pushAction); }, deleteItem(index) { - console.log("deleteItem", index, node); sourceFolderId = node.id!; sourceId = node.items[index].id!; }, addItem(value) { - console.log("additem", "value", value, "node", node); folderId = node.id!; moveModule(); }, moveItem(from, to) { - console.log("moveItem", node, from, to, node.id); }, }; @@ -638,18 +623,14 @@ export default function ModulePanel() { }; const node = convertRefTree(tree); function onCopy(type: boolean, id: string) { - console.log("onCopy", type, id); } function onSelect(type: boolean, id: string, meta: any) { setSelectedID(id); setSelectedType(type); - console.log("onSelect", type, id, meta) } function onDelete(type: boolean, id: string, node: NodeType) { - console.log("1111111111111111111111111", type, id, node); - if (type) { if (node.children.length) { messageInstance.error(trans("module.folderNotEmpty")) @@ -667,7 +648,7 @@ export default function ModulePanel() { ) ); } catch (error) { - console.error("Error: Delete module in extension:", error); + console.error("Error: Remove folder in extension:", error); throw error; } } @@ -701,61 +682,60 @@ export default function ModulePanel() { onCancel: () => {} }); } catch (error) { - console.error("Error: Delete module in extension:", error); + console.error("Error: Remove module in extension:", error); throw error; } } } - return ( - <> - {trans("rightPanel.moduleListTitle")} - {node ? - node={node!} - disable={!!searchValue} - unfoldAll={!!searchValue} - showSubInDragOverlay={false} - showDropInPositionLine={false} - showPositionLineDot - positionLineDotDiameter={4} - positionLineHeight={1} - itemHeight={25} - positionLineIndent={(path, dropInAsSub) => { - const indent = 2 + (path.length - 1) * 30; - if (dropInAsSub) { - return indent + 12; - } - return indent; - }} - renderItemContent={(params) => { - const { node, onToggleFold, onDelete: onDeleteTreeItem, ...otherParams } = params; - const resComp = node.data; - if (!resComp) { - return null; - } - const id = resComp.id; - const isFolder = resComp.isFolder; - return ( - onCopy(isFolder, id)} - onSelect={() => onSelect(isFolder, id, resComp)} - selectedID={selectedID} - setSelectedID={setSelectedID} - selectedType={selectedType} - setSelectedType={setSelectedType} - onDelete={() => { - (onDelete(isFolder, id, resComp)) - }} - {...otherParams} - /> - ); - }} - /> : empty} - - ); + <> + {trans("rightPanel.moduleListTitle")} + {node?.items.length ? + node={node!} + disable={!!searchValue} + unfoldAll={!!searchValue} + showSubInDragOverlay={false} + showDropInPositionLine={false} + showPositionLineDot + positionLineDotDiameter={4} + positionLineHeight={1} + itemHeight={25} + positionLineIndent={(path, dropInAsSub) => { + const indent = 2 + (path.length - 1) * 30; + if (dropInAsSub) { + return indent + 12; + } + return indent; + }} + renderItemContent={(params) => { + const { node, onToggleFold, onDelete: onDeleteTreeItem, ...otherParams } = params; + const resComp = node.data; + if (!resComp) { + return null; + } + const id = resComp.id; + const isFolder = resComp.isFolder; + return ( + onCopy(isFolder, id)} + onSelect={() => onSelect(isFolder, id, resComp)} + selectedID={selectedID} + setSelectedID={setSelectedID} + selectedType={selectedType} + setSelectedType={setSelectedType} + onDelete={() => { + (onDelete(isFolder, id, resComp)) + }} + {...otherParams} + /> + ); + }} + /> : empty} + + ); } diff --git a/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts b/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts index e4ca19920..4326cb2ec 100644 --- a/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts +++ b/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts @@ -110,7 +110,7 @@ export const folderReducer = createReducer(initialState, { action.payload.parentFolderId ?? "" ]?.map((e) => { if (e.folder && e.folderId === action.payload.folderId) { - return { ...action.payload, name: action.payload.name }; + return { ...e, name: action.payload.name}; } return e; }); From 4871649494ac76631d9e682e86ec5feea0e638ae Mon Sep 17 00:00:00 2001 From: Imiss-U1025 Date: Sat, 23 Nov 2024 08:51:21 -0500 Subject: [PATCH 22/36] Fixed an issue miss loading-indicator. --- client/packages/lowcoder/index.html | 2 ++ client/packages/lowcoder/src/comps/comps/rootComp.tsx | 3 ++- client/packages/lowcoder/src/index.ts | 4 ++-- .../lowcoder/src/pages/ApplicationV2/index.tsx | 3 ++- client/packages/lowcoder/src/pages/userAuth/index.tsx | 3 ++- client/packages/lowcoder/src/util/hideLoading.tsx | 10 ++++++++++ 6 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 client/packages/lowcoder/src/util/hideLoading.tsx diff --git a/client/packages/lowcoder/index.html b/client/packages/lowcoder/index.html index f3019a0cd..b9f940e01 100644 --- a/client/packages/lowcoder/index.html +++ b/client/packages/lowcoder/index.html @@ -28,6 +28,8 @@ display: flex; pointer-events: none; flex-direction: column; + top: 0; + z-index: 10000; } #loading svg { animation: breath 1s linear infinite; diff --git a/client/packages/lowcoder/src/comps/comps/rootComp.tsx b/client/packages/lowcoder/src/comps/comps/rootComp.tsx index 5fede0b07..83fe577c9 100644 --- a/client/packages/lowcoder/src/comps/comps/rootComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/rootComp.tsx @@ -34,7 +34,7 @@ import { ExternalEditorContext } from "util/context/ExternalEditorContext"; import { useUserViewMode } from "util/hooks"; import React from "react"; import { isEqual } from "lodash"; - +import {LoadingBarHideTrigger} from "@lowcoder-ee/util/hideLoading"; const EditorView = lazy( () => import("pages/editor/editorView"), ); @@ -138,6 +138,7 @@ const RootView = React.memo((props: RootViewProps) => {
{comp.children.queries.children[key].getView()}
))} + diff --git a/client/packages/lowcoder/src/index.ts b/client/packages/lowcoder/src/index.ts index 086d19d0e..2072fc849 100644 --- a/client/packages/lowcoder/src/index.ts +++ b/client/packages/lowcoder/src/index.ts @@ -24,7 +24,7 @@ if (!window.ResizeObserver) { window.ResizeObserver = ResizeObserver; } -function hideLoading() { +export function hideLoading() { // hide loading const node = document.getElementById("loading"); if (node) { @@ -42,7 +42,7 @@ debug(`REACT_APP_LOG_LEVEL:, ${REACT_APP_LOG_LEVEL}`); try { bootstrap(); - hideLoading(); + // hideLoading(); } catch (e) { log.error(e); } diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx index c6fd5f91f..fc2f7536a 100644 --- a/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx +++ b/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx @@ -73,7 +73,7 @@ import AppEditor from "../editor/AppEditor"; import { fetchDeploymentIdAction } from "@lowcoder-ee/redux/reduxActions/configActions"; import { getDeploymentId } from "@lowcoder-ee/redux/selectors/configSelectors"; import { SimpleSubscriptionContextProvider } from '@lowcoder-ee/util/context/SimpleSubscriptionContext'; - +import {LoadingBarHideTrigger} from "@lowcoder-ee/util/hideLoading"; const TabLabel = styled.div` font-weight: 500; `; @@ -222,6 +222,7 @@ export default function ApplicationHome() { return ( + (); @@ -50,6 +50,7 @@ export default function UserAuth() { fetchUserAfterAuthSuccess, }} > + diff --git a/client/packages/lowcoder/src/util/hideLoading.tsx b/client/packages/lowcoder/src/util/hideLoading.tsx new file mode 100644 index 000000000..f4c12c345 --- /dev/null +++ b/client/packages/lowcoder/src/util/hideLoading.tsx @@ -0,0 +1,10 @@ +import {useEffect} from "react"; +import {hideLoading} from "@lowcoder-ee/index"; + +export const LoadingBarHideTrigger = function(props: any) { + useEffect(() => { + setTimeout(() => hideLoading(), 300); + }, []); + + return <> +}; \ No newline at end of file From 9e5a05f2dfa1cfbed77915e61bfdc356e3ed3fc3 Mon Sep 17 00:00:00 2001 From: FalkWolsky Date: Sat, 23 Nov 2024 15:51:16 +0100 Subject: [PATCH 23/36] Excluding local definition for EE --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8758dff24..1dcb36aaa 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ application-dev-localhost.yaml server/api-service/lowcoder-server/src/main/resources/application-local-dev.yaml translations/locales/node_modules/ .vscode/settings.json +server/api-service/lowcoder-server/src/main/resources/application-local-dev-ee.yaml From 297ae81346109c5e6b4a0cdada410d628df22cb5 Mon Sep 17 00:00:00 2001 From: Imiss-U1025 Date: Sat, 23 Nov 2024 16:24:44 -0500 Subject: [PATCH 24/36] Fixed folder or content title overflow in module panel and applied it on bottomContent. --- .../DraggableTree/DroppableMenuItem.tsx | 2 +- .../src/pages/editor/bottom/BottomSidebar.tsx | 2 +- .../src/pages/editor/right/ModulePanel.tsx | 15 ++++++++++----- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/client/packages/lowcoder/src/components/DraggableTree/DroppableMenuItem.tsx b/client/packages/lowcoder/src/components/DraggableTree/DroppableMenuItem.tsx index 3d49e438a..7c9eac729 100644 --- a/client/packages/lowcoder/src/components/DraggableTree/DroppableMenuItem.tsx +++ b/client/packages/lowcoder/src/components/DraggableTree/DroppableMenuItem.tsx @@ -103,7 +103,7 @@ export default function DraggableMenuItem(props: IDraggableMenuItemProps) { > ⣿ -
+
{renderContent?.({ node: item, isOver, diff --git a/client/packages/lowcoder/src/pages/editor/bottom/BottomSidebar.tsx b/client/packages/lowcoder/src/pages/editor/bottom/BottomSidebar.tsx index 1e75ec141..03ff67c75 100644 --- a/client/packages/lowcoder/src/pages/editor/bottom/BottomSidebar.tsx +++ b/client/packages/lowcoder/src/pages/editor/bottom/BottomSidebar.tsx @@ -323,7 +323,7 @@ const HighlightBorder = styled.div<{ $active: boolean; $foldable: boolean; $leve max-width: 100%; flex: 1; display: flex; - padding-left: ${(props) => props.$level * 20 + (props.$foldable ? 0 : 14)}px; + padding-left: ${(props) => props.$level * 10 + (props.$foldable ? 0 : 14)}px; border-radius: 4px; border: 1px solid ${(props) => (props.$active ? BorderActiveColor : "transparent")}; align-items: center; diff --git a/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx b/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx index 3110647a4..a24b787d2 100644 --- a/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx +++ b/client/packages/lowcoder/src/pages/editor/right/ModulePanel.tsx @@ -38,6 +38,7 @@ const ItemWrapper = styled.div` } .module-container { display: flex; + width: 195px; } .module-icon { margin-right: 4px; @@ -167,6 +168,7 @@ interface ModuleItemProps { setSelectedType: (id: boolean) => void; resComp: NodeType; id: string; + $level: number; } function ModuleItem(props: ModuleItemProps) { @@ -179,7 +181,8 @@ function ModuleItem(props: ModuleItemProps) { selectedType, setSelectedType, resComp, - id + id, + $level, } = props; const dispatch = useDispatch(); const type = resComp.isFolder; @@ -243,8 +246,9 @@ function ModuleItem(props: ModuleItemProps) { >
-
- + props.$level * 20 + (props.$foldable ? 0 : 14)}px; + padding-left: ${(props) => props.$level * 10 + (props.$foldable ? 0 : 14)}px; border-radius: 4px; border: 1px solid ${(props) => (props.$active ? BorderActiveColor : "transparent")}; align-items: center; @@ -479,7 +483,8 @@ function ModuleSidebarItem(props: ModuleSidebarItemProps) { selectedType={selectedType} setSelectedType={setSelectedType} resComp = {resComp} - id = {id} + id={id} + $level={level} />} {!readOnly && !isOverlay && ( onDelete()}> From 895e184975af64c40d574bfeec4c03bb52da39cf Mon Sep 17 00:00:00 2001 From: Thomasr Date: Tue, 26 Nov 2024 09:33:11 -0500 Subject: [PATCH 25/36] Fixed enterprise login issue for newcomers --- .../domain/organization/service/OrganizationServiceImpl.java | 3 --- .../runner/migrations/job/MigrateAuthConfigJobImpl.java | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/organization/service/OrganizationServiceImpl.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/organization/service/OrganizationServiceImpl.java index 9a2bb24cc..a1358b39f 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/organization/service/OrganizationServiceImpl.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/organization/service/OrganizationServiceImpl.java @@ -91,9 +91,6 @@ public Mono createDefault(User user, boolean isSuperAdmin) { if (Boolean.TRUE.equals(join)) { return Mono.empty(); } - OrganizationDomain organizationDomain = new OrganizationDomain(); - organizationDomain.setConfigs(List.of(DEFAULT_AUTH_CONFIG)); - organization.setOrganizationDomain(organizationDomain); return create(organization, user.getId(), isSuperAdmin); }); }); diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/job/MigrateAuthConfigJobImpl.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/job/MigrateAuthConfigJobImpl.java index d86615959..a89eb4480 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/job/MigrateAuthConfigJobImpl.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/job/MigrateAuthConfigJobImpl.java @@ -8,6 +8,7 @@ import org.lowcoder.sdk.auth.AbstractAuthConfig; import org.lowcoder.sdk.config.AuthProperties; import org.lowcoder.sdk.config.CommonConfig; +import org.lowcoder.sdk.constants.AuthSourceConstants; import org.lowcoder.sdk.constants.WorkspaceMode; import org.lowcoder.sdk.util.IDUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -57,6 +58,6 @@ protected void setAuthConfigs2OrganizationDomain(Organization organization, List organization.setOrganizationDomain(domain); } authConfigs.forEach(abstractAuthConfig -> abstractAuthConfig.setId(IDUtils.generate())); - domain.setConfigs(authConfigs); + domain.setConfigs(authConfigs.stream().filter(authConfig -> !authConfig.getSource().equals(AuthSourceConstants.EMAIL)).toList()); } } From 837fa891c9ab71d3b003b46f8770c2695320ee18 Mon Sep 17 00:00:00 2001 From: Thomasr Date: Tue, 26 Nov 2024 10:53:27 -0500 Subject: [PATCH 26/36] change default page num to 1 --- .../org/lowcoder/api/datasource/DatasourceController.java | 6 +++--- .../org/lowcoder/api/datasource/DatasourceEndpoints.java | 6 +++--- .../java/org/lowcoder/api/query/LibraryQueryController.java | 2 +- .../java/org/lowcoder/api/query/LibraryQueryEndpoints.java | 2 +- .../lowcoder/api/query/LibraryQueryRecordController.java | 2 +- .../org/lowcoder/api/query/LibraryQueryRecordEndpoints.java | 2 +- .../org/lowcoder/api/usermanagement/GroupController.java | 2 +- .../org/lowcoder/api/usermanagement/GroupEndpoints.java | 2 +- .../lowcoder/api/usermanagement/OrganizationController.java | 2 +- .../lowcoder/api/usermanagement/OrganizationEndpoints.java | 2 +- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceController.java index 4d0071639..05476fbd7 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceController.java @@ -119,7 +119,7 @@ public Mono> getStructure(@PathVariable String */ @Override public Mono> listJsDatasourcePlugins(@RequestParam("appId") String applicationId, @RequestParam(required = false) String name, @RequestParam(required = false) String type, - @RequestParam(required = false, defaultValue = "0") int pageNum, + @RequestParam(required = false, defaultValue = "1s") int pageNum, @RequestParam(required = false, defaultValue = "0") int pageSize) { String objectId = gidService.convertApplicationIdToObjectId(applicationId); return fluxToPageResponseView(pageNum, pageSize, datasourceApiService.listJsDatasourcePlugins(objectId, name, type)); @@ -142,7 +142,7 @@ public Mono>> getPluginDynamicConfig( @SneakyThrows @Override public Mono> listOrgDataSources(@RequestParam(name = "orgId") String orgId, @RequestParam(required = false) String name, @RequestParam(required = false) String type, - @RequestParam(required = false, defaultValue = "0") int pageNum, + @RequestParam(required = false, defaultValue = "1") int pageNum, @RequestParam(required = false, defaultValue = "0") int pageSize) { if (StringUtils.isBlank(orgId)) { return ofError(BizError.INVALID_PARAMETER, "ORG_ID_EMPTY"); @@ -153,7 +153,7 @@ public Mono> listOrgDataSources(@RequestParam(name = "orgId" @Override public Mono> listAppDataSources(@RequestParam(name = "appId") String applicationId, @RequestParam(required = false) String name, @RequestParam(required = false) String type, - @RequestParam(required = false, defaultValue = "0") int pageNum, + @RequestParam(required = false, defaultValue = "1") int pageNum, @RequestParam(required = false, defaultValue = "0") int pageSize) { if (StringUtils.isBlank(applicationId)) { return ofError(BizError.INVALID_PARAMETER, "INVALID_APP_ID"); diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceEndpoints.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceEndpoints.java index d3608533d..775d70229 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceEndpoints.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceEndpoints.java @@ -101,7 +101,7 @@ public Mono> getStructure(@PathVariable String ) @GetMapping("/jsDatasourcePlugins") public Mono> listJsDatasourcePlugins(@RequestParam("appId") String applicationId, @RequestParam(required = false) String name, @RequestParam(required = false) String type, - @RequestParam(required = false, defaultValue = "0") int pageNum, + @RequestParam(required = false, defaultValue = "1") int pageNum, @RequestParam(required = false, defaultValue = "0") int pageSize); /** @@ -127,7 +127,7 @@ public Mono>> getPluginDynamicConfig( @JsonView(JsonViews.Public.class) @GetMapping("/listByOrg") public Mono> listOrgDataSources(@RequestParam(name = "orgId") String orgId, @RequestParam String name, @RequestParam String type, - @RequestParam(required = false, defaultValue = "0") int pageNum, + @RequestParam(required = false, defaultValue = "1") int pageNum, @RequestParam(required = false, defaultValue = "0") int pageSize); @Operation( @@ -140,7 +140,7 @@ public Mono> listOrgDataSources(@RequestParam(name = "orgId" @JsonView(JsonViews.Public.class) @GetMapping("/listByApp") public Mono> listAppDataSources(@RequestParam(name = "appId") String applicationId, @RequestParam String name, @RequestParam String type, - @RequestParam(required = false, defaultValue = "0") int pageNum, + @RequestParam(required = false, defaultValue = "1") int pageNum, @RequestParam(required = false, defaultValue = "0") int pageSize); @Operation( diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryController.java index be0e7de68..a7a5a320d 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryController.java @@ -46,7 +46,7 @@ public Mono>> dropDownList(@Request @Override public Mono> list(@RequestParam(required = false, defaultValue = "") String name, - @RequestParam(required = false, defaultValue = "0") int pageNum, + @RequestParam(required = false, defaultValue = "1") int pageNum, @RequestParam(required = false, defaultValue = "100") int pageSize) { var flux = libraryQueryApiService.listLibraryQueries(name) .flatMapMany(Flux::fromIterable); diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryEndpoints.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryEndpoints.java index c4acd3749..bf4b8f161 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryEndpoints.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryEndpoints.java @@ -40,7 +40,7 @@ public interface LibraryQueryEndpoints ) @GetMapping("/listByOrg") public Mono> list(@RequestParam(required = false, defaultValue = "") String name, - @RequestParam(required = false, defaultValue = "0") int pageNum, + @RequestParam(required = false, defaultValue = "1") int pageNum, @RequestParam(required = false, defaultValue = "100") int pageSize); @Operation( diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryRecordController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryRecordController.java index 31a1b8b4d..9db6a9ea2 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryRecordController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryRecordController.java @@ -30,7 +30,7 @@ public Mono delete(@PathVariable String libraryQueryRecordId) { @Override public Mono> getByLibraryQueryId(@RequestParam(name = "libraryQueryId") String libraryQueryId, - @RequestParam(required = false, defaultValue = "0") int pageNum, + @RequestParam(required = false, defaultValue = "1") int pageNum, @RequestParam(required = false, defaultValue = "100") int pageSize) { return fluxToPageResponseView(pageNum, pageSize, libraryQueryRecordApiService.getByLibraryQueryId(libraryQueryId).flatMapMany(Flux::fromIterable)); } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryRecordEndpoints.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryRecordEndpoints.java index 7fb642fb0..9f41f380d 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryRecordEndpoints.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryRecordEndpoints.java @@ -41,7 +41,7 @@ public interface LibraryQueryRecordEndpoints ) @GetMapping("/listByLibraryQueryId") public Mono> getByLibraryQueryId(@RequestParam(name = "libraryQueryId") String libraryQueryId, - @RequestParam(required = false, defaultValue = "0") int pageNum, + @RequestParam(required = false, defaultValue = "1") int pageNum, @RequestParam(required = false, defaultValue = "100") int pageSize); @Operation( diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupController.java index 4e7facb99..a3ed463f7 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupController.java @@ -119,7 +119,7 @@ public Mono>> getOrgGroups(@RequestParam(r @Override public Mono> getGroupMembers(@PathVariable String groupId, - @RequestParam(required = false, defaultValue = "0") int pageNum, + @RequestParam(required = false, defaultValue = "1") int pageNum, @RequestParam(required = false, defaultValue = "100") int pageSize) { String objectId = gidService.convertGroupIdToObjectId(groupId); return groupApiService.getGroupMembers(objectId, pageNum, pageSize) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupEndpoints.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupEndpoints.java index e2f8bfa7a..89e294628 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupEndpoints.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupEndpoints.java @@ -74,7 +74,7 @@ public Mono>> getOrgGroups(@RequestParam(r ) @GetMapping("/{groupId}/members") public Mono> getGroupMembers(@PathVariable String groupId, - @RequestParam(required = false, defaultValue = "0") int pageNum, + @RequestParam(required = false, defaultValue = "1") int pageNum, @RequestParam(required = false, defaultValue = "100") int pageSize); @Operation( diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationController.java index 2b2a9dd75..d43676ba5 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationController.java @@ -90,7 +90,7 @@ public Mono> deleteLogo(@PathVariable String orgId) { @Override public Mono> getOrgMembers(@PathVariable String orgId, - @RequestParam(required = false, defaultValue = "0") int pageNum, + @RequestParam(required = false, defaultValue = "1") int pageNum, @RequestParam(required = false, defaultValue = "1000") int pageSize) { String id = gidService.convertOrganizationIdToObjectId(orgId); return orgApiService.getOrganizationMembers(id, pageNum, pageSize) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationEndpoints.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationEndpoints.java index 38332e892..8fc9d5598 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationEndpoints.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationEndpoints.java @@ -95,7 +95,7 @@ public Mono> uploadLogo(@PathVariable String orgId, ) @GetMapping("/{orgId}/members") public Mono> getOrgMembers(@PathVariable String orgId, - @RequestParam(required = false, defaultValue = "0") int pageNum, + @RequestParam(required = false, defaultValue = "1") int pageNum, @RequestParam(required = false, defaultValue = "1000") int pageSize); @Operation( From 91215b2780b7411316398670c3b3317bf6ac0b79 Mon Sep 17 00:00:00 2001 From: Thomasr Date: Tue, 26 Nov 2024 13:22:30 -0500 Subject: [PATCH 27/36] fix default value --- .../java/org/lowcoder/api/datasource/DatasourceController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceController.java index 05476fbd7..695245c41 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceController.java @@ -119,7 +119,7 @@ public Mono> getStructure(@PathVariable String */ @Override public Mono> listJsDatasourcePlugins(@RequestParam("appId") String applicationId, @RequestParam(required = false) String name, @RequestParam(required = false) String type, - @RequestParam(required = false, defaultValue = "1s") int pageNum, + @RequestParam(required = false, defaultValue = "1") int pageNum, @RequestParam(required = false, defaultValue = "0") int pageSize) { String objectId = gidService.convertApplicationIdToObjectId(applicationId); return fluxToPageResponseView(pageNum, pageSize, datasourceApiService.listJsDatasourcePlugins(objectId, name, type)); From 29689c8e214a4ab2953d58cd1935218c490d53c7 Mon Sep 17 00:00:00 2001 From: Thomasr Date: Wed, 27 Nov 2024 01:34:04 -0500 Subject: [PATCH 28/36] Fixed pagination start value --- .../domain/organization/service/OrgMemberServiceImpl.java | 2 +- .../org/lowcoder/api/usermanagement/GroupApiServiceImpl.java | 2 +- .../java/org/lowcoder/api/usermanagement/OrgApiServiceImpl.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/organization/service/OrgMemberServiceImpl.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/organization/service/OrgMemberServiceImpl.java index 65c6a8945..fdf6127e6 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/organization/service/OrgMemberServiceImpl.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/organization/service/OrgMemberServiceImpl.java @@ -53,7 +53,7 @@ public Flux getOrganizationMembers(String orgId) { @Override public Flux getOrganizationMembers(String orgId, int page, int count) { - return biRelationService.getBySourceId(ORG_MEMBER, orgId, PageRequest.of(page, count)) + return biRelationService.getBySourceId(ORG_MEMBER, orgId, PageRequest.of(page - 1, count)) .map(OrgMember::from); } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupApiServiceImpl.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupApiServiceImpl.java index 07e97fc9d..1ae81589a 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupApiServiceImpl.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupApiServiceImpl.java @@ -98,7 +98,7 @@ public Mono getGroupMembers(String groupId, int page, .filter(Objects::nonNull) .toList(); var pageTotal = list.size(); - list = list.subList(page * count, Math.min(page * count + count, pageTotal)); + list = list.subList((page - 1) * count, count == 0 ? pageTotal : Math.min(page * count, pageTotal)); return Pair.of(list, pageTotal); }); }) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgApiServiceImpl.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgApiServiceImpl.java index 0a68beb8a..d6aa34203 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgApiServiceImpl.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgApiServiceImpl.java @@ -106,7 +106,7 @@ private Mono getOrgMemberListView(String orgId, int page, int .filter(Objects::nonNull) .collect(Collectors.toList()); var pageTotal = list.size(); - list = list.subList(page * count, Math.min(page * count + count, pageTotal)); + list = list.subList((page - 1) * count, count == 0 ? pageTotal : Math.min(page * count, pageTotal)); return Pair.of(list, pageTotal); }); }) From 3cf31b6249911944c30eceba4b1d499feec251c5 Mon Sep 17 00:00:00 2001 From: Thomasr Date: Wed, 27 Nov 2024 02:01:55 -0500 Subject: [PATCH 29/36] Fix total count in group list endpoint --- .../java/org/lowcoder/api/usermanagement/GroupController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupController.java index a3ed463f7..a7adcb6ec 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupController.java @@ -99,7 +99,7 @@ public Mono>> getOrgGroups(@RequestParam(r .filter(orgMember -> !orgMember.isAdmin() && !orgMember.isSuperAdmin() && devMembers.stream().noneMatch(devMember -> devMember.getUserId().equals(orgMember.getUserId()))).toList().size(); - var subList = groupList.subList((pageNum - 1) * pageSize, pageSize <= 0?groupList.size():pageNum * pageSize); + var subList = groupList.subList((pageNum - 1) * pageSize, pageSize <= 0?groupList.size():Math.min(pageNum * pageSize, groupList.size())); return new GroupListResponseView<>(ResponseView.SUCCESS, "", subList, @@ -107,7 +107,7 @@ public Mono>> getOrgGroups(@RequestParam(r totalAdminsAndDevelopers, totalDevelopersOnly, totalOtherMembers, - subList.size(), + groupList.size(), pageNum, pageSize); }) From 35b044aecf69e39e7d4131e06dcc35dd6b313598 Mon Sep 17 00:00:00 2001 From: Nikolay Angelov Date: Wed, 27 Nov 2024 11:20:55 +0200 Subject: [PATCH 30/36] fix: add null check to table row selection --- client/packages/lowcoder-core/lib/index.cjs | 226 +++++++++--------- client/packages/lowcoder-core/lib/index.js | 226 +++++++++--------- .../lowcoder-core/src/eval/codeNode.tsx | 2 +- 3 files changed, 227 insertions(+), 227 deletions(-) diff --git a/client/packages/lowcoder-core/lib/index.cjs b/client/packages/lowcoder-core/lib/index.cjs index 95905706e..7b061c66d 100644 --- a/client/packages/lowcoder-core/lib/index.cjs +++ b/client/packages/lowcoder-core/lib/index.cjs @@ -9,118 +9,118 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'defau var ___default = /*#__PURE__*/_interopDefaultLegacy(_); -/****************************************************************************** -Copyright (c) Microsoft Corporation. - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. -***************************************************************************** */ -/* global Reflect, Promise, SuppressedError, Symbol */ - -var extendStatics = function(d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); -}; - -function __extends(d, b) { - if (typeof b !== "function" && b !== null) - throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); -} - -var __assign = function() { - __assign = Object.assign || function __assign(t) { - for (var s, i = 1, n = arguments.length; i < n; i++) { - s = arguments[i]; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; - } - return t; - }; - return __assign.apply(this, arguments); -}; - -function __rest(s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) - t[p[i]] = s[p[i]]; - } - return t; -} - -function __decorate(decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -} - -function __awaiter(thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -} - -function __generator(thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (g && (g = 0, op[0] && (_ = 0)), _) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -} - -function __spreadArray(to, from, pack) { - if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { - if (ar || !(i in from)) { - if (!ar) ar = Array.prototype.slice.call(from, 0, i); - ar[i] = from[i]; - } - } - return to.concat(ar || Array.prototype.slice.call(from)); -} - -typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { - var e = new Error(message); - return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; +/****************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */ +/* global Reflect, Promise, SuppressedError, Symbol */ + +var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); +}; + +function __extends(d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +} + +var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; + +function __rest(s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +} + +function __decorate(decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +} + +function __awaiter(thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +} + +function __generator(thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +} + +function __spreadArray(to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || Array.prototype.slice.call(from)); +} + +typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { + var e = new Error(message); + return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; function isEqualArgs(args, cacheArgs, equals) { @@ -1789,7 +1789,7 @@ var CodeNode = /** @class */ (function (_super) { if ((pathsArr === null || pathsArr === void 0 ? void 0 : pathsArr[0]) === (options === null || options === void 0 ? void 0 : options.queryName)) return; // wait for lazy loaded comps to load before executing query on page load - if (!Object.keys(value).length && paths.size) { + if (value && !Object.keys(value).length && paths.size) { isFetching_1 = true; ready_1 = false; } diff --git a/client/packages/lowcoder-core/lib/index.js b/client/packages/lowcoder-core/lib/index.js index 28dc7a075..66045110c 100644 --- a/client/packages/lowcoder-core/lib/index.js +++ b/client/packages/lowcoder-core/lib/index.js @@ -1,118 +1,118 @@ import _ from 'lodash'; import { serialize, compile, middleware, prefixer, stringify } from 'stylis'; -/****************************************************************************** -Copyright (c) Microsoft Corporation. - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. -***************************************************************************** */ -/* global Reflect, Promise, SuppressedError, Symbol */ - -var extendStatics = function(d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); -}; - -function __extends(d, b) { - if (typeof b !== "function" && b !== null) - throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); -} - -var __assign = function() { - __assign = Object.assign || function __assign(t) { - for (var s, i = 1, n = arguments.length; i < n; i++) { - s = arguments[i]; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; - } - return t; - }; - return __assign.apply(this, arguments); -}; - -function __rest(s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) - t[p[i]] = s[p[i]]; - } - return t; -} - -function __decorate(decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -} - -function __awaiter(thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -} - -function __generator(thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (g && (g = 0, op[0] && (_ = 0)), _) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -} - -function __spreadArray(to, from, pack) { - if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { - if (ar || !(i in from)) { - if (!ar) ar = Array.prototype.slice.call(from, 0, i); - ar[i] = from[i]; - } - } - return to.concat(ar || Array.prototype.slice.call(from)); -} - -typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { - var e = new Error(message); - return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; +/****************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */ +/* global Reflect, Promise, SuppressedError, Symbol */ + +var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); +}; + +function __extends(d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +} + +var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; + +function __rest(s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +} + +function __decorate(decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +} + +function __awaiter(thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +} + +function __generator(thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +} + +function __spreadArray(to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || Array.prototype.slice.call(from)); +} + +typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { + var e = new Error(message); + return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; function isEqualArgs(args, cacheArgs, equals) { @@ -1781,7 +1781,7 @@ var CodeNode = /** @class */ (function (_super) { if ((pathsArr === null || pathsArr === void 0 ? void 0 : pathsArr[0]) === (options === null || options === void 0 ? void 0 : options.queryName)) return; // wait for lazy loaded comps to load before executing query on page load - if (!Object.keys(value).length && paths.size) { + if (value && !Object.keys(value).length && paths.size) { isFetching_1 = true; ready_1 = false; } diff --git a/client/packages/lowcoder-core/src/eval/codeNode.tsx b/client/packages/lowcoder-core/src/eval/codeNode.tsx index f5d31cd7f..2b67e7bbf 100644 --- a/client/packages/lowcoder-core/src/eval/codeNode.tsx +++ b/client/packages/lowcoder-core/src/eval/codeNode.tsx @@ -177,7 +177,7 @@ export class CodeNode extends AbstractNode> { if (pathsArr?.[0] === options?.queryName) return; // wait for lazy loaded comps to load before executing query on page load - if (!Object.keys(value).length && paths.size) { + if (value && !Object.keys(value).length && paths.size) { isFetching = true; ready = false; } From 28a7f2b4fda32dfd8a80cb5c75c5172e406bd5f8 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Wed, 20 Nov 2024 21:07:58 +0500 Subject: [PATCH 31/36] Show recent and archived snapshots for app --- .../lowcoder-design/src/icons/index.tsx | 1 + .../lowcoder/src/api/appSnapshotApi.ts | 22 +- .../comps/selectInputComp/stepControl.tsx | 2 +- .../lowcoder/src/pages/common/header.tsx | 5 +- .../lowcoder/src/pages/editor/appSnapshot.tsx | 209 +++++++++++------- .../reducers/uiReducers/appSnapshotReducer.ts | 5 +- .../redux/reduxActions/appSnapshotActions.ts | 19 +- .../src/redux/sagas/appSnapshotSagas.ts | 14 +- .../redux/selectors/appSnapshotSelector.ts | 6 +- 9 files changed, 188 insertions(+), 95 deletions(-) diff --git a/client/packages/lowcoder-design/src/icons/index.tsx b/client/packages/lowcoder-design/src/icons/index.tsx index 687d3516b..a538cb9bb 100644 --- a/client/packages/lowcoder-design/src/icons/index.tsx +++ b/client/packages/lowcoder-design/src/icons/index.tsx @@ -1,4 +1,5 @@ export { ReactComponent as AppSnapshotIcon } from "./v1/app-snapshot.svg"; +export { ReactComponent as ArchiveIcon } from "./remix/archive-fill.svg"; export { ReactComponent as HookCompDropIcon } from "./v1/hook-comp-drop.svg"; export { ReactComponent as HookCompIcon } from "./v1/hook-comp.svg"; diff --git a/client/packages/lowcoder/src/api/appSnapshotApi.ts b/client/packages/lowcoder/src/api/appSnapshotApi.ts index 18e98678e..572576605 100644 --- a/client/packages/lowcoder/src/api/appSnapshotApi.ts +++ b/client/packages/lowcoder/src/api/appSnapshotApi.ts @@ -22,18 +22,34 @@ export interface AppSnapshotDslResp extends ApiResponse { class AppSnapshotApi extends Api { static createSnapshotURL = "/application/history-snapshots"; static snapshotsURL = (appId: string) => `/application/history-snapshots/${appId}`; + static archiveSnapshotsURL = (appId: string) => `/application/history-snapshots/archive/${appId}`; static snapshotDslURL = (appId: string, snapshotId: string) => `/application/history-snapshots/${appId}/${snapshotId}`; - + static archiveSnapshotDslURL = (appId: string, snapshotId: string) => + `/application/history-snapshots/archive/${appId}/${snapshotId}`; static createSnapshot(request: CreateSnapshotPayload): AxiosPromise { return Api.post(AppSnapshotApi.createSnapshotURL, request); } - static getSnapshots(appId: string, pagination: PaginationParam): AxiosPromise { + static getSnapshots( + appId: string, + pagination: PaginationParam, + archived?: boolean, + ): AxiosPromise { + if (archived) { + return Api.get(AppSnapshotApi.archiveSnapshotsURL(appId), pagination); + } return Api.get(AppSnapshotApi.snapshotsURL(appId), pagination); } - static getSnapshotDsl(appId: string, snapshotId: string): AxiosPromise { + static getSnapshotDsl( + appId: string, + snapshotId: string, + archived?: boolean, + ): AxiosPromise { + if (archived) { + return Api.get(AppSnapshotApi.archiveSnapshotDslURL(appId, snapshotId)); + } return Api.get(AppSnapshotApi.snapshotDslURL(appId, snapshotId)); } } diff --git a/client/packages/lowcoder/src/comps/comps/selectInputComp/stepControl.tsx b/client/packages/lowcoder/src/comps/comps/selectInputComp/stepControl.tsx index 9278ec326..f8c916404 100644 --- a/client/packages/lowcoder/src/comps/comps/selectInputComp/stepControl.tsx +++ b/client/packages/lowcoder/src/comps/comps/selectInputComp/stepControl.tsx @@ -193,7 +193,7 @@ let StepControlBasicComp = (function () { > {props.options.map((option, index) => ( (currentAppInfo); const isSnapshotDslLoading = useSelector(isAppSnapshotDslFetching); const compInstance = useRootCompInstance(appInfo, true, true); + const [activeTab, setActiveTab] = useState("recent"); + + const isArchivedSnapshot = useMemo(() => activeTab === 'archive', [activeTab]); - const fetchSnapshotList = (page: number, onSuccess?: (snapshots: AppSnapshotList) => void) => { - dispatch(setSelectSnapshotId("")); + const fetchSnapshotList = useCallback((page: number, onSuccess?: (snapshots: AppSnapshotList) => void) => { + dispatch(setSelectSnapshotId("", isArchivedSnapshot)); application && dispatch( fetchSnapshotsAction({ applicationId: application.applicationId, page: page, size: PAGE_SIZE, + archived: isArchivedSnapshot, onSuccess: onSuccess, }) ); - }; + }, [application, activeTab]); - useMount(() => { + + useEffect(() => { if (!application) { return; } @@ -174,12 +183,17 @@ export const AppSnapshot = React.memo((props: { currentAppInfo: AppSummaryInfo } return; } dispatch( - fetchSnapshotDslAction(application.applicationId, snapshots.list[0].snapshotId, (res) => { - setLatestDsl(res); - }) + fetchSnapshotDslAction( + application.applicationId, + snapshots.list[0].snapshotId, + isArchivedSnapshot, + (res) => { + setLatestDsl(res); + } + ) ); }); - }); + }, [application, activeTab]); useEffect(() => { currentDsl && @@ -193,7 +207,10 @@ export const AppSnapshot = React.memo((props: { currentAppInfo: AppSummaryInfo } return; } setSelectedItemKey(snapshotId); - dispatch(setSelectSnapshotId(snapshotId === CURRENT_ITEM_KEY ? "" : snapshotId)); + dispatch(setSelectSnapshotId( + snapshotId === CURRENT_ITEM_KEY ? "" : snapshotId, + isArchivedSnapshot, + )); if (snapshotId === CURRENT_ITEM_KEY) { setAppInfo(currentAppInfo); return; @@ -202,56 +219,108 @@ export const AppSnapshot = React.memo((props: { currentAppInfo: AppSummaryInfo } return; } dispatch( - fetchSnapshotDslAction(application.applicationId, snapshotId, (dsl) => { - setAppInfo((i) => ({ - ...i, - dsl: dsl.applicationsDsl, - moduleDsl: dsl.moduleDSL, - })); - }) + fetchSnapshotDslAction( + application.applicationId, + snapshotId, + isArchivedSnapshot, + (dsl) => { + setAppInfo((i) => ({ + ...i, + dsl: dsl.applicationsDsl, + moduleDsl: dsl.moduleDSL, + })); + } + ) ); }, - [application, currentAppInfo, dispatch, setAppInfo, selectedItemKey] + [application, currentAppInfo, dispatch, setAppInfo, selectedItemKey, activeTab] ); - let snapShotContent; - if (snapshotsFetching || (currentPage === 1 && appSnapshots.length > 0 && !latestDsl)) { - snapShotContent = ; - } else if (appSnapshots.length <= 0 || !application) { - snapShotContent = ; - } else { - let snapshotItems: SnapshotItemProps[] = appSnapshots.map((snapshot, index) => { - return { - selected: selectedItemKey === snapshot.snapshotId, - title: - `${ - !latestDslChanged && currentPage === 1 && index === 0 - ? trans("history.currentVersionWithBracket") - : "" - }` + getOperationDesc(snapshot.context), - timeInfo: timestampToHumanReadable(snapshot.createTime), - userName: snapshot.userName, - onClick: () => { - onSnapshotItemClick(snapshot.snapshotId); - }, - }; - }); - if (currentPage === 1 && latestDslChanged) { - snapshotItems = [ - { - selected: selectedItemKey === CURRENT_ITEM_KEY, - title: trans("history.currentVersion"), - timeInfo: trans("history.justNow"), - userName: user.username, + const snapShotContent = useMemo(() => { + if (snapshotsFetching || (currentPage === 1 && appSnapshots.length > 0 && !latestDsl)) { + return ; + } else if (appSnapshots.length <= 0 || !application) { + return ; + } else { + let snapshotItems: SnapshotItemProps[] = appSnapshots.map((snapshot, index) => { + return { + selected: selectedItemKey === snapshot.snapshotId, + title: + `${ + !latestDslChanged && currentPage === 1 && index === 0 + ? trans("history.currentVersionWithBracket") + : "" + }` + getOperationDesc(snapshot.context), + timeInfo: timestampToHumanReadable(snapshot.createTime), + userName: snapshot.userName, onClick: () => { - onSnapshotItemClick(CURRENT_ITEM_KEY); + onSnapshotItemClick(snapshot.snapshotId); + }, + }; + }); + if (currentPage === 1 && latestDslChanged) { + snapshotItems = [ + { + selected: selectedItemKey === CURRENT_ITEM_KEY, + title: trans("history.currentVersion"), + timeInfo: trans("history.justNow"), + userName: user.username, + onClick: () => { + onSnapshotItemClick(CURRENT_ITEM_KEY); + }, }, - }, - ...snapshotItems, - ]; + ...snapshotItems, + ]; + } + return ; } - snapShotContent = ; - } + }, [ + user, + snapshotsFetching, + currentPage, + appSnapshots, + latestDsl, + application, + selectedItemKey, + latestDslChanged, + onSnapshotItemClick, + ]); + + const TabContent = useMemo(() => ( + <> + + {snapShotContent} + + + { + setCurrentPage(page); + fetchSnapshotList(page); + }} + total={totalCount} + pageSize={PAGE_SIZE} + showSizeChanger={false} + /> + + + ), [headerHeight, footerHeight, snapShotContent, currentPage, totalCount]); + + const tabConfigs = useMemo(() => [ + { + key: "recent", + title: "Recent", + icon: , + content: TabContent, + }, + { + key: "archive", + title: "Archive", + icon: , + content: TabContent, + } + ], [TabContent]); return ( }> @@ -262,31 +331,13 @@ export const AppSnapshot = React.memo((props: { currentAppInfo: AppSummaryInfo } compInstance={compInstance} /> - - - {trans("history.history")} - { - dispatch(setShowAppSnapshot(false)); - }} - /> - - - {snapShotContent} - - - { - setCurrentPage(page); - fetchSnapshotList(page); - }} - total={totalCount} - pageSize={PAGE_SIZE} - showSizeChanger={false} - /> - + { + setActiveTab(key); + }} + tabsConfig={tabConfigs} + activeKey={activeTab} + /> ); diff --git a/client/packages/lowcoder/src/redux/reducers/uiReducers/appSnapshotReducer.ts b/client/packages/lowcoder/src/redux/reducers/uiReducers/appSnapshotReducer.ts index 27d63e13f..156f8fee5 100644 --- a/client/packages/lowcoder/src/redux/reducers/uiReducers/appSnapshotReducer.ts +++ b/client/packages/lowcoder/src/redux/reducers/uiReducers/appSnapshotReducer.ts @@ -14,6 +14,7 @@ const initialState: AppSnapshotState = { showAppSnapshot: false, snapshotDslFetching: false, selectedSnapshotId: "", + isSelectedSnapshotIdArchived: false, }; const appSnapshotReducer = createReducer(initialState, { @@ -28,11 +29,12 @@ const appSnapshotReducer = createReducer(initialState, { }, [ReduxActionTypes.SET_SELECT_SNAPSHOT_ID]: ( state: AppSnapshotState, - action: ReduxAction<{ snapshotId: string }> + action: ReduxAction<{ snapshotId: string, archived?: boolean }> ): AppSnapshotState => { return { ...state, selectedSnapshotId: action.payload.snapshotId, + isSelectedSnapshotIdArchived: action.payload.archived, }; }, [ReduxActionTypes.FETCH_APP_SNAPSHOT_DSL]: (state: AppSnapshotState): AppSnapshotState => { @@ -115,6 +117,7 @@ export interface AppSnapshotState { appSnapshotCount: number; showAppSnapshot: boolean; selectedSnapshotId: string; + isSelectedSnapshotIdArchived?: boolean; } export default appSnapshotReducer; diff --git a/client/packages/lowcoder/src/redux/reduxActions/appSnapshotActions.ts b/client/packages/lowcoder/src/redux/reduxActions/appSnapshotActions.ts index 905d3a78d..3b52b1f19 100644 --- a/client/packages/lowcoder/src/redux/reduxActions/appSnapshotActions.ts +++ b/client/packages/lowcoder/src/redux/reduxActions/appSnapshotActions.ts @@ -11,10 +11,10 @@ export const setShowAppSnapshot = (show: boolean) => { }; }; -export const setSelectSnapshotId = (snapshotId: string) => { +export const setSelectSnapshotId = (snapshotId: string, archived?: boolean) => { return { type: ReduxActionTypes.SET_SELECT_SNAPSHOT_ID, - payload: { snapshotId: snapshotId }, + payload: { snapshotId: snapshotId, archived: archived }, }; }; @@ -33,6 +33,7 @@ export const createSnapshotAction = (payload: CreateSnapshotPayload) => { export type FetchSnapshotsPayload = { applicationId: string; + archived: boolean; onSuccess?: (snapshots: AppSnapshotList) => void; } & PaginationParam; @@ -46,17 +47,24 @@ export const fetchSnapshotsAction = (payload: FetchSnapshotsPayload) => { export type FetchSnapshotDslPayload = { applicationId: string; snapshotId: string; + archived?: boolean; onSuccess: (res: AppSnapshotDslInfo) => void; }; export const fetchSnapshotDslAction = ( appId: string, snapshotId: string, + archived: boolean, onSuccess: (res: AppSnapshotDslInfo) => void ): ReduxAction => { return { type: ReduxActionTypes.FETCH_APP_SNAPSHOT_DSL, - payload: { applicationId: appId, snapshotId: snapshotId, onSuccess: onSuccess }, + payload: { + applicationId: appId, + snapshotId: snapshotId, + archived: archived, + onSuccess: onSuccess, + }, }; }; @@ -64,12 +72,14 @@ export type RecoverSnapshotPayload = { applicationId: string; snapshotId: string; snapshotCreateTime: number; + isArchivedSnapshot?: boolean; }; export const recoverSnapshotAction = ( appId: string, snapshotId: string, - snapshotCreateTime: number + snapshotCreateTime: number, + isArchivedSnapshot?: boolean, ): ReduxAction => { return { type: ReduxActionTypes.RECOVER_APP_SNAPSHOT, @@ -77,6 +87,7 @@ export const recoverSnapshotAction = ( applicationId: appId, snapshotId: snapshotId, snapshotCreateTime: snapshotCreateTime, + isArchivedSnapshot, }, }; }; diff --git a/client/packages/lowcoder/src/redux/sagas/appSnapshotSagas.ts b/client/packages/lowcoder/src/redux/sagas/appSnapshotSagas.ts index 266beeb5d..a111d7e84 100644 --- a/client/packages/lowcoder/src/redux/sagas/appSnapshotSagas.ts +++ b/client/packages/lowcoder/src/redux/sagas/appSnapshotSagas.ts @@ -42,7 +42,11 @@ export function* fetchAppSnapshotsSaga(action: ReduxAction = yield call( AppSnapshotApi.getSnapshots, action.payload.applicationId, - { page: action.payload.page, size: action.payload.size } + { + page: action.payload.page, + size: action.payload.size, + }, + action.payload.archived, ); if (validateResponse(response)) { action.payload.onSuccess && action.payload.onSuccess(response.data.data); @@ -63,7 +67,8 @@ export function* fetchAppSnapshotDslSaga(action: ReduxAction = yield call( AppSnapshotApi.getSnapshotDsl, action.payload.applicationId, - action.payload.snapshotId + action.payload.snapshotId, + action.payload.archived, ); if (validateResponse(response)) { // replace dsl @@ -81,11 +86,12 @@ export function* fetchAppSnapshotDslSaga(action: ReduxAction) { try { - const { applicationId, snapshotId, snapshotCreateTime } = action.payload; + const { applicationId, snapshotId, snapshotCreateTime, isArchivedSnapshot } = action.payload; const response: AxiosResponse = yield call( AppSnapshotApi.getSnapshotDsl, applicationId, - snapshotId + snapshotId, + isArchivedSnapshot, ); if (validateResponse(response)) { // record history record diff --git a/client/packages/lowcoder/src/redux/selectors/appSnapshotSelector.ts b/client/packages/lowcoder/src/redux/selectors/appSnapshotSelector.ts index c2b7af89f..189139250 100644 --- a/client/packages/lowcoder/src/redux/selectors/appSnapshotSelector.ts +++ b/client/packages/lowcoder/src/redux/selectors/appSnapshotSelector.ts @@ -5,9 +5,13 @@ export const showAppSnapshotSelector = (state: AppState) => { }; export const getSelectedAppSnapshot = (state: AppState) => { - return state.ui.appSnapshot.appSnapshots.find( + const selectedSnapshot = state.ui.appSnapshot.appSnapshots.find( (s) => s.snapshotId === state.ui.appSnapshot.selectedSnapshotId ); + return { + selectedSnapshot, + isArchivedSnapshot: state.ui.appSnapshot.isSelectedSnapshotIdArchived, + } }; export const appSnapshotsSelector = (state: AppState) => { From 18be56b76d51502bf6d6158875879c207950dd32 Mon Sep 17 00:00:00 2001 From: Thomasr Date: Wed, 27 Nov 2024 02:13:34 -0500 Subject: [PATCH 32/36] #1322: Fix object comparing using Objects.equals() --- .../authentication/service/AuthenticationApiServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/service/AuthenticationApiServiceImpl.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/service/AuthenticationApiServiceImpl.java index b4e3e2c4d..df1c9e1d1 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/service/AuthenticationApiServiceImpl.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/service/AuthenticationApiServiceImpl.java @@ -228,7 +228,7 @@ protected Connection getAuthConnection(AuthUser authUser, User user) { return user.getConnections() .stream() .filter(connection -> authUser.getSource().equals(connection.getSource()) - && connection.getRawId().equals(authUser.getUid())) + && Objects.equals(connection.getRawId(), authUser.getUid())) .findFirst() .get(); } From 7a67082c84fb95ee5a620cf553105239062691fa Mon Sep 17 00:00:00 2001 From: FalkWolsky Date: Wed, 27 Nov 2024 14:05:10 +0100 Subject: [PATCH 33/36] Starting with APITemplate Plugin --- .../apiTemplate/apitemplateiov2_api.yaml | 2279 +++++++++++++++++ 1 file changed, 2279 insertions(+) create mode 100644 server/node-service/src/plugins/apiTemplate/apitemplateiov2_api.yaml diff --git a/server/node-service/src/plugins/apiTemplate/apitemplateiov2_api.yaml b/server/node-service/src/plugins/apiTemplate/apitemplateiov2_api.yaml new file mode 100644 index 000000000..2a323d7eb --- /dev/null +++ b/server/node-service/src/plugins/apiTemplate/apitemplateiov2_api.yaml @@ -0,0 +1,2279 @@ +openapi: "3.0.0" +info: + description: | + # Introduction + + + Welcome to the [APITemplate.io](https://apitemplate.io) API v2! + + APITemplate.io provides PDF generation services including [Template-based PDF generation](https://apitemplate.io/pdf-generation-api/), [HTML to PDF](https://apitemplate.io/html-to-pdf-api/), and [URL to PDF conversions](https://apitemplate.io/create-pdf-from-url/), as well as an [image generation API](https://apitemplate.io/image-generation-api/). + + This page contains the documentation on how to use APITemplate.io through API calls. With the APITemplate.io API, you can create PDF documents and images, as well as manage your templates. + + Our API is built on RESTful HTTP, so you can utilize any HTTP/REST library of your choice in your preferred programming language to interact with APITemplate.io's API. + + **Steps to produce PDFs/Images** + 1. Design your template(s) using our intuitive drag-and-drop template editor or the HTML editor and save it. + 2. Integrate your workflow, either with platforms like Zapier, Make.com/Integromat, Bubble.io, or any programming languages that support REST API, to send us the JSON data along with the template ID/URL/or HTML content. + 3. Our REST API will then return a download URL for the images (in PNG and JPEG formats) or PDFs. + + # Authentication + Upon signing up for an account, an API key will be generated for you. If needed, you can reset this API key via the web console (under the "API Integration" section). + + To integrate with our services, you need to authenticate with the APITemplate.io API. Provide your secret key in the request header using the X-API-KEY field. + + + # Content Type and CORS + + **Request Content-Type** + The Content-Type for POST and GET requests is set to application/json. + + **Cross-Origin Resource Sharing** + This API features Cross-Origin Resource Sharing (CORS) implemented in compliance with [W3C spec](https://www.w3.org/TR/cors/). + And that allows cross-domain communication from the browser. + All responses have a wildcard same-origin which makes them completely public and accessible to everyone, including any code on any site. + + + + # Regional API endpoint(s) + A regional API endpoint is intended for customers in the same region. The data for the requests and generated PDFs/images are processed and stored within the region. + + The regions are: + + | Region | Endpoint | Max Timeout (Seconds) | Max Payload Size(MB)** | + |----------------------|-------------------------------------|-----------------------|-------------------------| + | Default (Singapore) | https://rest.apitemplate.io | 100 | 1 | + | Europe (Frankfurt) | https://rest-de.apitemplate.io | 100 | 1 | + | US East (N. Virginia)| https://rest-us.apitemplate.io | 100 | 1 | + | Australia (Sydney) | https://rest-au.apitemplate.io | 30 | 6 | + + + Alternative Regions: + | Region | Endpoint | Max Timeout (Seconds) | Max Payload Size(MB)** | + |----------------------|-------------------------------------|-----------------------|-------------------------| + | Default (Singapore) | https://rest-alt.apitemplate.io | 30 | 6 | + | Europe (Frankfurt) | https://rest-alt-de.apitemplate.io | 30 | 6 | + | US East (N. Virginia)| https://rest-alt-us.apitemplate.io | 30 | 6 | + + ** Note: + - Payload size applies to request and response + - If "export_type" is set to `json` which output file that on AWS S3 doesn't have the limitation + - If the "export_type" is set to `file` which returns binary data of the generated PDF, the file size of the generated PDF is limited to either 6MB or 1MB based on the region + + + + Other regions are available on request, contact us at hello@apitemplate.io for more information + + # Rate limiting + Our API endpoints use IP-based rate limiting to ensure fair usage and prevent abuse. Users are allowed to make up to **100 requests per 10 seconds**. This rate limit is designed to accommodate a reasonable volume of requests while maintaining optimal performance for all users. + + However, if you exceed this limit and make additional requests, you will receive a response with HTTP code 429. This status code indicates that you have reached the rate limit and need to wait before making further requests. + + + version: Version 2.0 + title: APITemplate.io API Reference + termsOfService: 'https://apitemplate.io/privacy-policy/' + contact: + email: hello@apitemplate.io + url: https://apitemplate.io + x-logo: + url: 'images/logo_new2_with_text2.png' + altText: APITemplate.io logo + license: + name: Apache 2.0 + url: 'http://www.apache.org/licenses/LICENSE-2.0.html' +servers: + - url: https://rest.apitemplate.io + - url: https://rest-au.apitemplate.io + - url: https://rest-de.apitemplate.io + - url: https://rest-us.apitemplate.io + + +security: + - ApiKeyAuth: [] + + + + + + + +paths: + /v2/create-pdf: + post: + summary: Create a PDF + operationId: create-pdf + description: 'This endpoint creates a PDF file with JSON data and your template. We support synchoronus and asynchronous PDF generation.' + tags: + - API Integration + security: + - ApiKeyAuth: [] + parameters: + - $ref: "#/components/parameters/paramTemplateID" + - $ref: "#/components/parameters/paramExportType" + - $ref: "#/components/parameters/paramExportInBase64" + - $ref: "#/components/parameters/paramExpiration" + - $ref: "#/components/parameters/paramOutputHTML" + - $ref: "#/components/parameters/paramOutputFormat" + - $ref: "#/components/parameters/paramFileName" + - $ref: "#/components/parameters/paramDirectDownload" + - $ref: "#/components/parameters/paramCloudStorage" + - $ref: "#/components/parameters/paramLoadDataFrom" + - $ref: "#/components/parameters/paramGenerationDelay" + - $ref: "#/components/parameters/paramImageResampleRes" + - $ref: "#/components/parameters/paramResizeImages" + - $ref: "#/components/parameters/paramResizeMaxWidth" + - $ref: "#/components/parameters/paramResizeMaxHeight" + - $ref: "#/components/parameters/paramResizeFormat" + - $ref: "#/components/parameters/paramPOSTACTIONS3FILEKEY" + - $ref: "#/components/parameters/paramPOSTACTIONS3BUCKET" + - $ref: "#/components/parameters/paramMeta" + - $ref: "#/components/parameters/paramAsync" + - $ref: "#/components/parameters/paramWebhook" + - $ref: "#/components/parameters/paramWebhookMethod" + - $ref: "#/components/parameters/paramWebhookHeaders" + + + requestBody: + required: true + content: + application/json: + schema: + type: object + description: JSON data + example: + invoice_number: "INV38379" + date: "2021-09-30" + currency: "USD" + total_amount: 82542.56 + + responses: + '200': + description: Returns status and output file + content: + application/json: + schema: + $ref: '#/components/schemas/ResponseSuccessPDFFile' + + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + x-code-samples: + - lang: cURL + source: | + curl --header "Content-Type: application/json" \ + -H 'X-API-KEY: 6fa6g2pdXGIyHRhVlGh7U56Ada1eF' \ + --data '{ "invoice_number": "INV38379", "date": "2021-09-30", "currency": "USD", "total_amount": 82542.56 }' \ + "https://rest.apitemplate.io/v2/create-pdf?template_id=79667b2b1876e347" + + - lang: 'Python' + source: | + import requests, json + + def main(): + api_key = "6fa6g2pdXGIyHRhVlGh7U56Ada1eF" + template_id = "79667b2b1876e347" + + data = { + "invoice_number": "INV38379", + "date": "2021-09-30", + "currency": "USD", + "total_amount": 82542.56 + } + + response = requests.post( + F"https://rest.apitemplate.io/v2/create-pdf?template_id={template_id}", + headers = {"X-API-KEY": F"{api_key}"}, + json= data + ) + + if __name__ == "__main__": + main() + + - lang: PHP + source: | + + + - lang: Node.js + source: | + const https = require('https'); + const http = require('http'); + const { URL } = require('url'); + + (async () => { + let resp = await httpPost( + 'https://rest.apitemplate.io/v2/create-pdf?template_id=79667b2b1876e347', + '{ "invoice_number": "INV38379", "date": "2021-09-30", "currency": "USD", "total_amount": 82542.56 }', + '6fa6g2pdXGIyHRhVlGh7U56Ada1eF' + ); + console.log(resp); + })(); + + + async function httpPost(url_api, data, apiKey){ + const uri = new URL(url_api); + const fx = uri.protocol === 'https:' ? https : http; + const opts = { + method: 'POST', + hostname: uri.hostname, + port: uri.port, + path: `${uri.pathname}${uri.search==null?"":uri.search}`, + protocol: uri.protocol, + headers: { + 'Content-Type': 'application/json', + 'Content-Length': data.length, + "X-API-KEY": apiKey + } + }; + + return new Promise((resolve, reject) => { + const req = fx.request(opts, (res) => { + res.setEncoding('utf8'); + let responseBody = ''; + res.on('data', (chunk) => responseBody += chunk); + res.on('end', () => resolve(responseBody)); + }); + + req.on('error', (err) => reject(err)); + req.write(data) + req.end(); + }); + } + + - lang: CSharp + source: | + using System; + using System.IO; + using System.Net.Http; + using System.Text.Json; + using System.Threading.Tasks; + + namespace csharp + { + class ReturnContent{ + public string download_url{get;set;} + public string status{get;set;} + } + + class Program + { + static async Task Main(string[] args) + { + var api_key = "6fa6g2pdXGIyHRhVlGh7U56Ada1eF"; + var template_id = "79667b2b1876e347"; + var url = $"https://rest.apitemplate.io/v2/create-pdf?template_id={template_id}"; + + var data = new { + invoice_number = "INV38379", + date = "2021-09-30", + currency = "USD", + total_amount = 82542.56 + }; + + + var json_content = JsonSerializer.Serialize(data); + var buffer = System.Text.Encoding.UTF8.GetBytes(json_content); + var byteContent = new ByteArrayContent(buffer); + + Console.WriteLine(json_content); + + var client = new HttpClient(); + client.DefaultRequestHeaders.Add("X-API-KEY",api_key); + var response = await client.PostAsync(url,byteContent); + var ret = await response.Content.ReadAsStringAsync(); + + var returnContent = JsonSerializer.Deserialize(ret); + + if(returnContent.status=="success"){ + Console.WriteLine($"Downloading {returnContent.download_url}..."); + var download_response = await client.GetAsync(returnContent.download_url); + using (var stream = await download_response.Content.ReadAsStreamAsync()) + { + var fileInfo = new FileInfo("image.jpeg"); + using (var fileStream = fileInfo.OpenWrite()) + { + await stream.CopyToAsync(fileStream); + } + } + } + } + } + } + + + + +############################################################################################################# +############################################################################################################# +############################################################################################################# + + /v2/create-image: + post: + summary: Create an Image + operationId: create-image + description: | + This endpoint creates a JPEG file(along with PNG) with JSON data and your template + + + + tags: + - API Integration + security: + - ApiKeyAuth: [] + parameters: + - $ref: "#/components/parameters/paramTemplateID" + + - in: query + name: output_image_type + schema: + type: string + required: false + description: | + - Output image type(JPEG or PNG format), default to `all`. Options are `all`, `jpegOnly`,`pngOnly`. + example: '1' + + - $ref: "#/components/parameters/paramExpiration" + - $ref: "#/components/parameters/paramCloudStorage" + - $ref: "#/components/parameters/paramGenerationDelay" + - $ref: "#/components/parameters/paramPOSTACTIONS3FILEKEY" + - $ref: "#/components/parameters/paramPOSTACTIONS3BUCKET" + - $ref: "#/components/parameters/paramMeta" + + requestBody: + required: true + content: + application/json: + schema: + type: object + description: | + JSON data + - The following is the json format in the post body to generate an image + ``` + { + "overrides": [ + { + "name": "", + "property_1": "", + "property_2": "", + "property_3": "", + ... + }, + { + "name": "", + "property_2": "", + ... + } + ] + } + ``` + example: + overrides: + - name: text_1 + text: hello world + textBackgroundColor: 'rgba(246, 243, 243, 0)' + - name: image_1 + src: 'https://via.placeholder.com/150' + + + responses: + '200': + description: Returns status and output file + content: + application/json: + schema: + $ref: '#/components/schemas/ResponseSuccessImageFile' + + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + x-code-samples: + - lang: cURL + source: | + curl --header "Content-Type: application/json" \ + -H 'X-API-KEY: 6fa6g2pdXGIyHRhVlGh7U56Ada1eF' \ + --data '{ "overrides":[ { "name":"text_1", "text":"hello world", "textBackgroundColor":"rgba(246, 243, 243, 0)" }, { "name":"image_1", "src":"https://via.placeholder.com/150" } ] }' \ + "https://rest.apitemplate.io/v2/create-image?template_id=79667b2b1876e347" + + - lang: 'Python' + source: | + import requests, json + + def main(): + api_key = "6fa6g2pdXGIyHRhVlGh7U56Ada1eF" + template_id = "79667b2b1876e347" + + data = { + "overrides":[ + { + "name":"text_1", + "text":"hello world", + "textBackgroundColor":"rgba(246, 243, 243, 0)" + }, + { + "name":"image_1", + "src":"https://via.placeholder.com/150" + } + ] + } + + response = requests.post( + F"https://rest.apitemplate.io/v2/create-image?template_id={template_id}", + headers = {"X-API-KEY": F"{api_key}"}, + json= data + ) + + if __name__ == "__main__": + main() + + - lang: PHP + source: | + + + - lang: Node.js + source: | + const https = require('https'); + const http = require('http'); + const { URL } = require('url'); + + (async () => { + let resp = await httpPost( + 'https://rest.apitemplate.io/v2/create-pdf?template_id=79667b2b1876e347', + '{ "overrides":[ { "name":"text_1", "text":"hello world", "textBackgroundColor":"rgba(246, 243, 243, 0)" }, { "name":"image_1", "src":"https://via.placeholder.com/150" } ] }', + '6fa6g2pdXGIyHRhVlGh7U56Ada1eF' + ); + console.log(resp); + })(); + + + async function httpPost(url_api, data, apiKey){ + const uri = new URL(url_api); + const fx = uri.protocol === 'https:' ? https : http; + const opts = { + method: 'POST', + hostname: uri.hostname, + port: uri.port, + path: `${uri.pathname}${uri.search==null?"":uri.search}`, + protocol: uri.protocol, + headers: { + 'Content-Type': 'application/json', + 'Content-Length': data.length, + "X-API-KEY": apiKey + } + }; + + return new Promise((resolve, reject) => { + const req = fx.request(opts, (res) => { + res.setEncoding('utf8'); + let responseBody = ''; + res.on('data', (chunk) => responseBody += chunk); + res.on('end', () => resolve(responseBody)); + }); + + req.on('error', (err) => reject(err)); + req.write(data) + req.end(); + }); + } + + - lang: CSharp + source: | + using System; + using System.IO; + using System.Net.Http; + using System.Text.Json; + using System.Threading.Tasks; + + namespace csharp + { + class ReturnContent{ + public string download_url{get;set;} + public string status{get;set;} + } + + class Program + { + static async Task Main(string[] args) + { + var api_key = "6fa6g2pdXGIyHRhVlGh7U56Ada1eF"; + var template_id = "79667b2b1876e347"; + var url = $"https://rest.apitemplate.io/v2/create-image?template_id={template_id}"; + + var json_content = '{ "overrides":[ { "name":"text_1", "text":"hello world", "textBackgroundColor":"rgba(246, 243, 243, 0)" }, { "name":"text_2", "text":"Hi there" } ] }'; + + var buffer = System.Text.Encoding.UTF8.GetBytes(json_content); + var byteContent = new ByteArrayContent(buffer); + + Console.WriteLine(json_content); + + var client = new HttpClient(); + client.DefaultRequestHeaders.Add("X-API-KEY",api_key); + var response = await client.PostAsync(url,byteContent); + var ret = await response.Content.ReadAsStringAsync(); + + var returnContent = JsonSerializer.Deserialize(ret); + + if(returnContent.status=="success"){ + Console.WriteLine($"Downloading {returnContent.download_url}..."); + var download_response = await client.GetAsync(returnContent.download_url); + using (var stream = await download_response.Content.ReadAsStreamAsync()) + { + var fileInfo = new FileInfo("image.jpeg"); + using (var fileStream = fileInfo.OpenWrite()) + { + await stream.CopyToAsync(fileStream); + } + } + } + } + } + } + + + + +############################################################################################################# +############################################################################################################# +############################################################################################################# + + /v2/create-pdf-from-html: + post: + summary: Create a PDF from HTML + operationId: create-pdf-from-html + description: | + - This endpoint creates a PDF file from HTML with JSON data + tags: + - API Integration + security: + - ApiKeyAuth: [] + parameters: + - $ref: "#/components/parameters/paramExportType" + - $ref: "#/components/parameters/paramExpiration" + - $ref: "#/components/parameters/paramOutputFormat" + - $ref: "#/components/parameters/paramFileName" + - $ref: "#/components/parameters/paramDirectDownload" + - $ref: "#/components/parameters/paramCloudStorage" + - $ref: "#/components/parameters/paramGenerationDelay" + - $ref: "#/components/parameters/paramImageResampleRes" + - $ref: "#/components/parameters/paramResizeImages" + - $ref: "#/components/parameters/paramResizeMaxWidth" + - $ref: "#/components/parameters/paramResizeMaxHeight" + - $ref: "#/components/parameters/paramResizeFormat" + - $ref: "#/components/parameters/paramPOSTACTIONS3FILEKEY" + - $ref: "#/components/parameters/paramPOSTACTIONS3BUCKET" + - $ref: "#/components/parameters/paramMeta" + - $ref: "#/components/parameters/paramAsync" + - $ref: "#/components/parameters/paramWebhook" + - $ref: "#/components/parameters/paramWebhookMethod" + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + body: + type: string + description: | + The HTML body content for the PDF. This property supports HTML markup and can include Jinja2 syntax (e.g {{name}}). The value of {{name}} will be replaced with the actual value provided in the data object. + example:

hello world {{name}}

+ + css: + type: string + description: | + The CSS styles to be applied to the PDF. This property should contain valid CSS markup and should also include the style tag. + example: '' + data: + type: object + description: | + The data object containing values for dynamic content in the HTML body. This object should include properties with corresponding values. + example: {name: "This is a title"} + settings: + $ref: '#/components/schemas/PDFGenerationSettingsObject' + + + + responses: + '200': + description: Returns status and output file + content: + application/json: + schema: + $ref: '#/components/schemas/ResponseSuccessPDFFile' + + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + x-code-samples: + - lang: 'Python' + source: | + import requests, json + + def main(): + api_key = "6fa6g2pdXGIyHRhVlGh7U56Ada1eF" + template_id = "79667b2b1876e347" + + data = { + "body": "

hello world {{name}}

", + "css": "", + "data": { + "name": "This is a title" + }, + "settings": { + "paper_size": "A4", + "orientation": "1", + "header_font_size": "9px", + "margin_top": "40", + "margin_right": "10", + "margin_bottom": "40", + "margin_left": "10", + "print_background": "1", + "displayHeaderFooter": true, + "custom_header": "\n\n \n \n \n \n \n
", + "custom_footer": "\n\n \n \n \n \n \n
" + } + } + + response = requests.post( + F"https://rest.apitemplate.io/v2/create-pdf-from-html", + headers = {"X-API-KEY": F"{api_key}"}, + json= data + ) + + if __name__ == "__main__": + main() + +############################################################################################################# +############################################################################################################# +############################################################################################################# + + /v2/create-pdf-from-url: + post: + summary: Create a PDF from URL + operationId: create-pdf-from-url + description: | + - This endpoint creates a PDF file from a URL + tags: + - API Integration + security: + - ApiKeyAuth: [] + parameters: + - $ref: "#/components/parameters/paramExportType" + - $ref: "#/components/parameters/paramExpiration" + - $ref: "#/components/parameters/paramOutputFormat" + - $ref: "#/components/parameters/paramFileName" + - $ref: "#/components/parameters/paramDirectDownload" + - $ref: "#/components/parameters/paramCloudStorage" + - $ref: "#/components/parameters/paramGenerationDelay" + - $ref: "#/components/parameters/paramImageResampleRes" + - $ref: "#/components/parameters/paramResizeImages" + - $ref: "#/components/parameters/paramResizeMaxWidth" + - $ref: "#/components/parameters/paramResizeMaxHeight" + - $ref: "#/components/parameters/paramResizeFormat" + - $ref: "#/components/parameters/paramPOSTACTIONS3FILEKEY" + - $ref: "#/components/parameters/paramPOSTACTIONS3BUCKET" + - $ref: "#/components/parameters/paramMeta" + - $ref: "#/components/parameters/paramAsync" + - $ref: "#/components/parameters/paramWebhook" + - $ref: "#/components/parameters/paramWebhookMethod" + + + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + url: + type: string + description: | + The URL + example: https://en.wikipedia.org/wiki/Sceloporus_malachiticus + settings: + $ref: '#/components/schemas/PDFGenerationSettingsObject' + + + + responses: + '200': + description: Returns status and output file + content: + application/json: + schema: + $ref: '#/components/schemas/ResponseSuccessPDFFile' + + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + x-code-samples: + - lang: 'Python' + source: | + import requests, json + + def main(): + api_key = "6fa6g2pdXGIyHRhVlGh7U56Ada1eF" + template_id = "79667b2b1876e347" + + data = { + "url": "https://en.wikipedia.org/wiki/Sceloporus_malachiticus", + "settings": { + "paper_size": "A4", + "orientation": "1", + "header_font_size": "9px", + "margin_top": "40", + "margin_right": "10", + "margin_bottom": "40", + "margin_left": "10", + "print_background": "1", + "displayHeaderFooter": true, + "custom_header": "\n\n \n \n \n \n \n
", + "custom_footer": "\n\n \n \n \n \n \n
" + } + } + + response = requests.post( + F"https://rest.apitemplate.io/v2/create-pdf-from-url", + headers = {"X-API-KEY": F"{api_key}"}, + json= data + ) + + if __name__ == "__main__": + main() + + + +############################################################################################################# +############################################################################################################# +############################################################################################################# + + /v2/list-objects: + get: + summary: List Generated Objects + operationId: list-objects + description: | + Retrieves all the generated PDFs and images + + tags: + - API Integration + security: + - ApiKeyAuth: [] + parameters: + - in: query + name: limit + schema: + type: string + required: false + description: Retrieve only the number of records specified. Default to 300 + example: 300 + - in: query + name: offset + schema: + type: string + required: false + description: Offset is used to skip the number of records from the results. Default to 0 + example: 0 + - in: query + name: template_id + schema: + type: string + required: false + description: Filtered by template id + example: 00377b2b1e0ee394 + - in: query + name: transaction_type + schema: + type: string + required: false + description: Filtered by transaction type, options are `PDF`, `JPEG` or `MERGE` + example: MERGE + - in: query + name: transaction_ref + schema: + type: string + required: false + description: Transaction reference + example: 4adfhg-d0e8-7399-9335-717a881dd91 + + responses: + '200': + description: Returns status and output file + content: + application/json: + schema: + $ref: '#/components/schemas/ResponseSuccessListObjects' + + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + x-code-samples: + - lang: cURL + source: | + curl --header "Content-Type: application/json" \ + -H 'X-API-KEY: 6fa6g2pdXGIyHRhVlGh7U56Ada1eF' \ + "https://rest.apitemplate.io/v2/list-objects" + + - lang: 'Python' + source: | + import requests, json + + def main(): + api_key = "6fa6g2pdXGIyHRhVlGh7U56Ada1eF" + + response = requests.get( + F"https://rest.apitemplate.io/v2/list-objects", + headers = {"X-API-KEY": F"{api_key}"}, + ) + + if __name__ == "__main__": + main() + + - lang: PHP + source: | + + + - lang: Node.js + source: | + const https = require('https'); + const http = require('http'); + const { URL } = require('url'); + + (async () => { + let resp = await httpGet( + 'https://rest.apitemplate.io/v2/list-objects', + 'f6caMToxOjRySHV6dTRldU9JTVNobDg' + ); + console.log(resp); + })(); + + async function httpGet(url_api, apiKey){ + const uri = new URL(url_api); + const fx = uri.protocol === 'https:' ? https : http; + const opts = { + method: 'GET', + hostname: uri.hostname, + port: uri.port, + path: `${uri.pathname}${uri.search==null?"":uri.search}`, + protocol: uri.protocol, + headers: { + "X-API-KEY": apiKey + } + }; + + return new Promise((resolve, reject) => { + const req = fx.get(opts, (res) => { + res.setEncoding('utf8'); + let responseBody = ''; + res.on('data', (chunk) => responseBody += chunk); + res.on('end', () =>resolve(responseBody)); + }); + req.on('error', (err) => reject(err)); + }); + } + + +############################################################################################################# +############################################################################################################# +############################################################################################################# + + /v2/delete-object: + get: + summary: Delete an Object + operationId: delete-object + description: | + Delete a PDF or an image from CDN and mark the transaction as deleted + + tags: + - API Integration + security: + - ApiKeyAuth: [] + parameters: + - in: query + name: transaction_ref + schema: + type: string + required: true + description: Object transaction reference + example: 1618d386-2343-3d234-b9c7-99c82bb9f104 + + + responses: + '200': + description: Returns status and output file + content: + application/json: + schema: + $ref: '#/components/schemas/ResponseSuccessDeleteObject' + + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + x-code-samples: + - lang: cURL + source: | + curl --header "Content-Type: application/json" \ + -H 'X-API-KEY: 6fa6g2pdXGIyHRhVlGh7U56Ada1eF' \ + "https://rest.apitemplate.io/v2/delete-object?transaction_ref=1618d386-2343-3d234-b9c7-99c82bb9f104" + + - lang: 'Python' + source: | + import requests, json + + def main(): + api_key = "6fa6g2pdXGIyHRhVlGh7U56Ada1eF" + + response = requests.get( + F"https://rest.apitemplate.io/v2/delete-object?transaction_ref=1618d386-2343-3d234-b9c7-99c82bb9f104", + headers = {"X-API-KEY": F"{api_key}"}, + ) + + if __name__ == "__main__": + main() + + - lang: PHP + source: | + + + - lang: Node.js + source: | + const https = require('https'); + const http = require('http'); + const { URL } = require('url'); + + (async () => { + let resp = await httpGet( + 'https://rest.apitemplate.io/v2/delete-object?transaction_ref=1618d386-2343-3d234-b9c7-99c82bb9f104', + 'f6caMToxOjRySHV6dTRldU9JTVNobDg' + ); + console.log(resp); + })(); + + async function httpGet(url_api, apiKey){ + const uri = new URL(url_api); + const fx = uri.protocol === 'https:' ? https : http; + const opts = { + method: 'GET', + hostname: uri.hostname, + port: uri.port, + path: `${uri.pathname}${uri.search==null?"":uri.search}`, + protocol: uri.protocol, + headers: { + "X-API-KEY": apiKey + } + }; + + return new Promise((resolve, reject) => { + const req = fx.get(opts, (res) => { + res.setEncoding('utf8'); + let responseBody = ''; + res.on('data', (chunk) => responseBody += chunk); + res.on('end', () =>resolve(responseBody)); + }); + req.on('error', (err) => reject(err)); + }); + } + + + +############################################################################################################# +############################################################################################################# +############################################################################################################# + + /v2/list-templates: + get: + summary: List Templates + operationId: list-templates + description: | + Retrieves the information of templates + + tags: + - Template Management + security: + - ApiKeyAuth: [] + parameters: + - in: query + name: limit + schema: + type: string + required: false + description: Retrieve only the number of records specified. Default to 300 + example: "300" + - in: query + name: offset + schema: + type: string + required: false + description: Offset is used to skip the number of records from the results. Default to 0 + example: "0" + - in: query + name: format + schema: + type: string + required: false + description: To filter the templates by either 'PDF' or 'JPEG' + example: JPEG + - in: query + name: template_id + schema: + type: string + required: false + description: To filter the templates by template id + example: 00377b2b1e0ee394 + - in: query + name: group_name + schema: + type: string + required: false + description: To filter the templates by the group name + example: custom + - in: query + name: with_layer_info + schema: + type: string + required: false + description: Return along with layer information for image templates, 0=false , 1=true. Default to '0' + example: 0 + + responses: + '200': + description: Returns status and output file + content: + application/json: + schema: + $ref: '#/components/schemas/ResponseSuccessListTemplates' + + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + x-code-samples: + - lang: cURL + source: | + curl --header "Content-Type: application/json" \ + -H 'X-API-KEY: 6fa6g2pdXGIyHRhVlGh7U56Ada1eF' \ + "https://rest.apitemplate.io/v2/list-templates" + + - lang: 'Python' + source: | + import requests, json + + def main(): + api_key = "6fa6g2pdXGIyHRhVlGh7U56Ada1eF" + + response = requests.get( + F"https://rest.apitemplate.io/v2/list-templates", + headers = {"X-API-KEY": F"{api_key}"}, + ) + + if __name__ == "__main__": + main() + + - lang: PHP + source: | + + + - lang: Node.js + source: | + const https = require('https'); + const http = require('http'); + const { URL } = require('url'); + + (async () => { + let resp = await httpGet( + 'https://rest.apitemplate.io/v2/list-templates', + 'f6caMToxOjRySHV6dTRldU9JTVNobDg' + ); + console.log(resp); + })(); + + async function httpGet(url_api, apiKey){ + const uri = new URL(url_api); + const fx = uri.protocol === 'https:' ? https : http; + const opts = { + method: 'GET', + hostname: uri.hostname, + port: uri.port, + path: `${uri.pathname}${uri.search==null?"":uri.search}`, + protocol: uri.protocol, + headers: { + "X-API-KEY": apiKey + } + }; + + return new Promise((resolve, reject) => { + const req = fx.get(opts, (res) => { + res.setEncoding('utf8'); + let responseBody = ''; + res.on('data', (chunk) => responseBody += chunk); + res.on('end', () =>resolve(responseBody)); + }); + req.on('error', (err) => reject(err)); + }); + } + + + + /v2/get-template: + get: + summary: Get PDF template + operationId: get-template + description: | + Retrieves information of the PDF template (**This is an experimental API, contact support to learn more**) + + tags: + - Template Management + security: + - ApiKeyAuth: [] + parameters: + - in: query + name: template_id + schema: + type: string + required: false + description: Your template id, it can be obtained in the web console(Manage Templates) + example: 00377b2b1e0ee394 + + responses: + '200': + description: Returns status and template information + content: + application/json: + schema: + $ref: '#/components/schemas/ResponseSuccessTemplate' + + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + x-code-samples: + - lang: cURL + source: | + curl --header "Content-Type: application/json" \ + -H 'X-API-KEY: 6fa6g2pdXGIyHRhVlGh7U56Ada1eF' \ + "https://rest.apitemplate.io/v2/get-template?template_id=cd890b2b199c5c42" + + + + /v2/update-template: + post: + summary: Update PDF Template + operationId: update-template + description: 'This endpoint updates PDF template (**This is an experimental API, contact support to learn more**)' + tags: + - Template Management + security: + - ApiKeyAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + template_id: + type: string + description: | + Your template id, it can be obtained in the web console(Manage Templates) + example: '00377b2b1e0ee394' + body: + type: string + description: | + The HTML body + example: | +

Title

+ css: + type: string + description: | + The css + example: | + {body{ background: white;} + + #settings: + # type: string + # description: | + # Settings of the template, the followings is an example: + # ```json + # { + # "paper_size":"A4", + # "orientation":"1", + # "print_background":"1", + # "margin_top":"40", + # "margin_bottom":"40", + # "margin_right":"40", + # "margin_left":"40", + # "header_right":"{{pageNumber}}/{{totalPages}}", + # "footer_center":"{{pageNumber}}/{{totalPages}}", + # "header_center":"Sample Invoice", + # "header_font_size":"11px", + # "header_left":"{{date}}", + # "footer_left":"{{date}}", + # "custom_header":"", + # "footer_font_size":"11px" + # } + # ``` + # example: '{"paper_size":"A4","orientation":"1","print_background":"1","margin_top":"40","margin_bottom":"40","margin_right":"40","margin_left":"40","header_right":"{{pageNumber}}/{{totalPages}}","footer_center":"{{pageNumber}}/{{totalPages}}","header_center":"Sample Invoice","header_font_size":"11px","header_left":"{{date}}","footer_left":"{{date}}","custom_header":"","footer_font_size":"11px"}' + + required: + - template_id + responses: + '200': + description: Returns status and output file + content: + application/json: + schema: + $ref: '#/components/schemas/ResponseSuccess' + + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + x-code-samples: + - lang: cURL + source: | + curl -X POST \ + --header "Content-Type: application/json" \ + -H 'X-API-KEY: 6fa6g2pdXGIyHRhVlGh7U56Ada1eF' \ + --data '{"template_id": "d4477b2b2348d03a","body":"

this is a title

"}' \ + "https://rest.apitemplate.io/v2/update-template" + + + +############################################################################################################# +############################################################################################################# +############################################################################################################# + + + + /v2/merge-pdfs: + post: + summary: Join/Merge multiple PDFs + operationId: merge-pdfs + description: 'This endpoint merges/joins multiple PDF URLs into a single PDF file' + tags: + - PDF Manipulation API + security: + - ApiKeyAuth: [] + parameters: + - $ref: "#/components/parameters/paramPOSTACTIONS3FILEKEY" + - $ref: "#/components/parameters/paramPOSTACTIONS3BUCKET" + - $ref: "#/components/parameters/paramMeta" + + + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + urls: + type: array + items: + type: object + + description: | + URL array. We support normal http/https URLs and data URLs + - Normal URLs: URLs start with http/https, e.g: "https://fileserver.com/a1.pdf") + - Data URLs: URLs prefixed with the "data:" scheme, e.g "data:application/pdf;base64,JVBERi0xLjIg...[truncated]" + example: ['https://fileserver.com/a1.pdf', 'https://fileserver.com/b2.pdf', 'data:application/pdf;base64,JVBERi0xLjIg...[truncated]'] + + export_type: + type: string + description: | + - Either `file` or `json`(Default). + - The option `json` returns a JSON object, and the output PDF is stored on a CDN. + - The option `file` returns binary data of the generated PDF(Secure and completely private) and the response HTTP header Content-Disposition is set to attachment. It has a file size limit of 6MB. + example: 'json' + + expiration: + type: integer + description: | + - Expiration of the generated PDF in minutes(default to `0`, store permanently) + - Use `0` to store on cdn permanently + - Or use the range between `1` minute and `43200` minutes(30 days) to specify the expiration of the generated PDF + example: 5 + + cloud_storage: + type: integer + description: | + - Upload the generated PDFs/images to our storage CDN, default to `1`. If you have configured `Post Action` to upload the PDFs/Images to your own S3, please set it to `0`. + example: 1 + + required: + - urls + responses: + '200': + description: Returns status and output file + content: + application/json: + schema: + $ref: '#/components/schemas/ResponseSuccessSingleFile' + + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + x-code-samples: + - lang: cURL + source: | + curl -X POST \ + --header "Content-Type: application/json" \ + -H 'X-API-KEY: 6fa6g2pdXGIyHRhVlGh7U56Ada1eF' \ + --data '{ "urls": ["https://fileserver.com/a1.pdf","https://fileserver.com/b2.pdf"] }' \ + "https://rest.apitemplate.io/v2/merge-pdfs" + + - lang: 'Python' + source: | + import requests, json + + def main(): + api_key = "6fa6g2pdXGIyHRhVlGh7U56Ada1eF" + + json_payload = { + "urls": ["https://fileserver.com/a1.pdf","https://fileserver.com/b2.pdf"] , + "output_file": "output.pdf", + } + + response = requests.post( + F"https://rest.apitemplate.io/v2/merge-pdfs", + headers = {"X-API-KEY": F"{api_key}"}, + json = json_payload + ) + + print(response.content) + + if __name__ == "__main__": + main() + + + + + + +############################################################################################################# +############################################################################################################# +############################################################################################################# + + + + + + + + + + + + + + + + + + + + + + + + + + + + +############################################################################################################# +############################################################################################################# +############################################################################################################# + + +components: + securitySchemes: + ApiKeyAuth: # arbitrary name for the security scheme + type: apiKey + in: header # can be "header", "query" or "cookie" + name: X-API-KEY # name of the header, query parameter or cookie + description: | + An API key is needed to be set in the Authorization header of every API call. + For additional support you can contact us. + + - APITemplate.io expects the API key to be part of all API requests to the server in a header in this format: + ``` + X-API-KEY: [API_KEY] + ``` + + - Optionally we also support Authorization header + ``` + Authorization: Token [API_KEY] + ``` + + **Note: You must replace the API KEY(6fa6g2pdXGIyHRhVlGh7U56Ada1eF) with your API key in the request samples.** + schemas: + Error: + type: object + required: + - status + - message + properties: + status: + type: string + description: 'Value of the status: error' + example: 'error' + message: + type: string + description: 'Error message' + example: 'This is an error message' + + ResponseSuccess: + type: object + properties: + status: + type: string + description: Status + example: success + + ResponseSuccessTemplate: + type: object + properties: + status: + type: string + description: Status + example: success + template_id: + type: string + description: 'Template ID' + example: 'cd890b2b199c5c42' + body: + type: string + description: HTML body of the template + example: | +

Title

+ css: + type: string + description: CSS of the template + example: | + body{background: white} + settings: + type: string + description: Print settings of the template + example: | + {"paper_size":"A4","orientation":"1","print_background":"1","margin_top":"40","margin_bottom":"40","margin_right":"40","margin_left":"40","header_right":"{{pageNumber}}/{{totalPages}}","footer_center":"{{pageNumber}}/{{totalPages}}","header_center":"Sample Invoice","header_font_size":"11px","header_left":"{{date}}","footer_left":"{{date}}","custom_header":"","footer_font_size":"11px"} + + ResponseSuccessPDFFile: + type: object + properties: + status: + type: string + description: Status + example: success + download_url: + type: string + description: 'Download URL' + example: 'https://bucket.s3.amazonaws.com/91f62769-69e4-48bf.pdf' + template_id: + type: string + description: 'Template ID' + example: 'cd890b2b199c5c42' + total_pages: + type: integer + description: 'Page count' + example: 4 + transaction_ref: + type: string + description: Transaction reference + example: a0430897-2c94-40e1-a09b-57403d811ceb + post_actions: + type: array + items: + type: object + properties: + action: + type: string + name: + type: string + bucket: + type: string + status: + type: string + file: + type: string + + + + example: + - action: S3 + name: "S3 Storage" + bucket: "alphacloud-test-bucket" + status: "success" + file: "s3://alphacloud-test-bucket/ab2e1bf7-cefa-42c7-929f-38d92b8bf8bf.pdf" + + + ResponseSuccessImageFile: + type: object + properties: + status: + type: string + description: Status + example: success + download_url: + type: string + description: 'Download URL' + example: 'https://bucket.s3.amazonaws.com/91f62769-69e4-48bf.jpeg' + download_url_png: + type: string + description: 'Download URL PNG' + example: 'https://bucket.s3.amazonaws.com/91f62769-69e4-48bf.png' + template_id: + type: string + description: 'Template ID' + example: 'cd890b2b199c5c42' + transaction_ref: + type: string + description: Transaction reference + example: a0430897-2c94-40e1-a09b-57403d811ceb + post_actions: + type: array + items: + type: object + properties: + action: + type: string + name: + type: string + bucket: + type: string + status: + type: string + file: + type: string + + example: + - action: S3 + name: "S3 Storage" + bucket: "alphacloud-test-bucket" + status: "success" + file: "s3://alphacloud-test-bucket/91f62769-69e4-48bf.png" + - action: S3 + name: "S3 Storage" + bucket: "alphacloud-test-bucket" + status: "success" + file: "s3://alphacloud-test-bucket/91f62769-69e4-48bf.jpg" + + ResponseSuccessListTemplates: + type: object + properties: + status: + type: string + example: success + templates: + type: array + items: + type: object + properties: + template_id: + type: string + name: + type: string + status: + type: string + format: + type: string + created_at: + type: string + updated_at: + type: string + group_name: + type: string + example: + - template_id: 12577b29420496 + name: Positive Review + status: ACTIVE + format: JPEG + created_at: '2021-10-15T06:29:01.308Z' + updated_at: '2021-10-15T13:03:43.615Z' + group_name: '' + - template_id: 004271e0ee394 + name: Test Template PDF + status: ACTIVE + format: PDF + created_at: '2021-10-09T09:57:52.224Z' + updated_at: '2021-10-16T11:18:10.613Z' + group_name: '' + - template_id: 8bf77213e06b670 + name: New Template + status: ACTIVE + format: PDF + created_at: '2021-10-09T08:54:49.486Z' + updated_at: '2021-10-09T09:54:44.667Z' + group_name: '' + + ResponseSuccessListObjects: + type: object + properties: + status: + type: string + example: success + objects: + type: array + items: + type: object + properties: + transaction_ref: + type: string + description: + type: string + source: + type: string + meta: + type: string + transaction_type: + type: string + primary_url: + type: string + secondary_url: + type: string + deleted_at: + type: string + deletion_status: + type: integer + ip_address: + type: string + created_at: + type: string + example: + - transaction_ref: e9c46f03-1840-44dc-bae7-f280e0be98a9 + description: null + source: null + meta: 'inv-23ejh23bh' + transaction_type: JPEG + primary_url: 'https://pub-cdn.apitemplate.io/e9c46f03-1840-44dc-bae7-f280e0be98a9.jpeg' + secondary_url: 'https://pub-cdn.apitemplate.io/e9c46f03-1840-44dc-bae7-f280e0be98a9.png' + deleted_at: null + deletion_status: 0 + ip_address: "1.222.242.231" + created_at: '2021-10-16T12:08:59.281Z' + - transaction_ref: c973f544-fb56-465d-a1bd-35ff0e4b77e7 + description: null + source: null + meta: 'inv-45ekdjkdbh' + transaction_type: PDF + primary_url: >- + https://pub-cdn.apitemplate.io/2021/10/c973f544-fb56-465d-a1bd-35ff0e4b77e7.pdf + secondary_url: '' + deleted_at: null + deletion_status: 0 + ip_address: "1.222.242.231" + created_at: '2021-10-16T12:07:34.478Z' + - transaction_ref: 5ee5e0aa-4431-4d17-b94a-24ac859a5e71 + description: null + source: null + meta: 'inv-klkjbr34ded' + transaction_type: JPEG + primary_url: 'https://pub-cdn.apitemplate.io/5ee5e0aa-4431-4d17-b94a-24ac859a5e71.jpeg' + secondary_url: 'https://pub-cdn.apitemplate.io/5ee5e0aa-4431-4d17-b94a-24ac859a5e71.png' + deleted_at: null + deletion_status: 0 + ip_address: "1.222.242.231" + created_at: '2021-10-16T12:05:59.111Z' + + + + + ResponseSuccessDeleteObject: + type: object + properties: + status: + type: string + example: success + transaction_ref: + example: 1618d386-2343-3d234-b9c7-99c82bb9f104 + + ResponseSuccessSingleFile: + type: object + properties: + status: + type: string + description: 'Status' + example: 'success' + primary_url: + type: string + description: 'Generated PDF document' + example: 'https://craftmypdf.com/output.pdf' + total_pages: + type: integer + description: 'Page count' + example: 4 + transaction_ref: + type: string + description: Transaction reference + example: a0430897-2c94-40e1-a09b-57403d811ceb + + ResponseSuccessQueryImageTemplate: + type: object + properties: + status: + type: string + description: 'Status' + example: 'success' + width: + type: integer + description: 'Width' + example: 1024 + height: + type: integer + description: 'Height' + example: 1024 + layers: + type: array + items: + type: object + description: Array of layers + example: | + [ + { + "name": "text_1", + "type": "textbox", + "subtype": "textbox", + "y": 50, + "x": 50, + "width": 629.82, + "height": 406.8, + "fontSize": 120, + "fontWeight": "normal", + "fontFamily": "Anton", + "fontStyle": "normal", + "text": "Type ~something~ ::here::", + "stroke": null, + "strokeWidth": 0, + "opacity": 1, + "backgroundColor": "", + "textAlign": "left", + "splitByGrapheme": false, + "textBackgroundColor": "rgba(246, 243, 243, 0)", + "color": "#FFB029" + }, + { + "name": "rect_1", + "type": "rect", + "subtype": "rect", + "y": 101.9, + "x": 708.82, + "width": 300, + "height": 300, + "stroke": "grey", + "strokeWidth": 3, + "opacity": 1, + "backgroundColor": "", + "color": "#BEF4FF" + } + ] + + + PDFGenerationSettingsObject: + type: object + description: | + The settings object contains various properties to configure the PDF generation. + properties: + paper_size: + type: string + description: | + Specifies the paper size for the PDF. The available options are Letter, Legal, Tabloid, Ledger, A0, A1, A2, A3, A4, A5,A6 or custom. custom dimensions specified as "custom_width" and "custom_height". + custom_width: + type: string + description: | + Custom width for the custom paper size. Valid units are mm, px and cm. eg: 30mm + custom_height: + type: string + description: | + Custom height for the custom paper size. Valid units are mm, px and cm. eg: 30mm + orientation: + type: string + description: | + Specifies the orientation of the PDF. The available options are "1" for portrait and "2" for landscape. + header_font_size: + type: string + description: | + Specifies the font size for the header in the PDF. + margin_top: + type: string + description: | + Specify the top margin for the PDF in millimeters (mm). + margin_right: + type: string + description: | + Specify the right margin for the PDF in millimeters (mm). + margin_bottom: + type: string + description: | + Specify the bottom margin for the PDF in millimeters (mm). + margin_left: + type: string + description: | + Specify the left margin for the PDF in millimeters (mm). + print_background: + type: string + description: | + Specifies whether to print the background graphics and colors in the PDF. Set to "1" to include backgrounds or "0" to exclude them. + displayHeaderFooter: + type: boolean + description: | + Specifies whether to display the header and footer in the PDF. Set to true to include the header and footer or false to exclude them. + custom_header: + type: string + description: | + Specify custom HTML markup for the headerof the PDF. These properties should contain valid HTML markup, including any necessary CSS styles. + custom_footer: + type: string + description: | + Specify custom HTML markup for the footer of the PDF. These properties should contain valid HTML markup, including any necessary CSS styles. + + example: + paper_size: "A4" + orientation: "1" + header_font_size: "9px" + margin_top: "40" + margin_right: "10" + margin_bottom: "40" + margin_left: "10" + print_background: "1" + displayHeaderFooter: true + custom_header: "\n\n \n \n \n \n \n
" + custom_footer: "\n\n \n \n \n \n \n
" + + + + parameters: + paramTemplateID: + in: query + name: template_id + schema: + type: string + required: true + description: Your template id, it can be obtained in the web console + example: 00377b2b1e0ee394 + + paramExportType: + in: query + name: export_type + schema: + type: string + required: false + description: | + - Either `file` or `json`(Default). + - The option `json` returns a JSON object, and the output PDF is stored on a CDN. Use this with the parameter `expiration` + - The option `file` returns binary data of the generated PDF(Secure and completely private) and the response HTTP header Content-Disposition is set to attachment. + example: 'json' + paramExportInBase64: + in: query + name: export_in_base64 + schema: + type: string + required: false + description: | + - If export_type = `file`, the PDF can be downloaded in binary or base64 format. The value is either `1` or `0`(Default). + - The export_in_base64 is set `0` is to download the PDF in binary + - The export_in_base64 is set `1` is to download the PDF in base64 format + + example: '0' + paramLoadDataFrom: + in: query + name: load_data_from + schema: + type: string + required: false + description: | + Load JSON data from a remote URL instead of the request body. If load_data_from is specified, the JSON data in the request will be ignored. + + example: 'https://mydata.com/get-json-data?invoice=j3hbski2uia' + + + paramExpiration: + in: query + name: expiration + schema: + type: integer + required: false + description: | + - Expiration of the generated PDF in minutes(default to `0`, store permanently) + - Use `0` to store on cdn permanently + - Or use the range between `1` minute and `10080` minutes(7 days) to specify the expiration of the generated PDF + example: 5 + + paramOutputHTML: + in: query + name: output_html + schema: + type: string + required: false + description: | + - Either `1` or `0`(Default). + - To enable output of html content, set the value to `1` and it will return in the JSON response as html_url field (as a URL) + example: '0' + + + paramOutputFormat: + in: query + name: output_format + schema: + type: string + required: false + description: | + - Either `pdf`(Default) or `html`. + - It's generating PDF by default. However, you can specify output_format=html to generate only HTML(It will return in the JSON response as download_url field as a URL). + example: 'pdf' + + paramFileName: + in: query + name: filename + schema: + type: string + required: false + description: | + - Default to UUID (e.g 0c93bd9e-9ebb-4634-a70f-de9131848416.pdf). Use this to specify custom file name, it should end with `.pdf` + example: 'invoice_89326.pdf' + + paramImageResampleRes: + in: query + name: image_resample_res + schema: + type: string + required: false + description: | + - We embed the original images by default, meaning large PDF file sizes. Specifying the option 'image_resample_res' helps reduce the PDF file size by downsampling the images of the current PDF to a resolution(in DPI). Common values are 72, 96, 150, 300 and 600. + example: '150' + + paramResizeImages: + in: query + name: resize_images + schema: + type: boolean + required: false + description: | + - Preprocess images or re-size images in the PDF, either `1`=true or `0`=false. Default to '0' + - If `resize_images` is set to `1`, specify the `resize_max_width`, `resize_max_height` in pixels. + - Images to be resized need to satisfy the following conditions: + - The images with the content-type `image/jpeg`, `image/jpg` or `image/png` + - The image URLs with the extension `.jpg`, `.jpeg` or `.png` + example: '0' + + paramResizeMaxWidth: + in: query + name: resize_max_width + schema: + type: integer + required: false + description: | + - If `resize_images` is set to `1`, specify the maximum width of the image in pixels. Default to '1000' + example: '1000' + + paramResizeMaxHeight: + in: query + name: resize_max_height + schema: + type: integer + required: false + description: | + - If `resize_images` is set to `1`, specify the maximum height of the image in pixels. Default to '1000' + example: '1000' + + paramResizeFormat: + in: query + name: resize_format + schema: + type: string + required: false + description: | + - If `resize_images` is set to `1`, specify the format of the image. Either `jpeg` or `png` + example: 'jpeg' + + + paramDirectDownload: + in: query + name: direct_download + schema: + type: string + required: false + description: | + - ContentDisposition set to attachment. 1=true, 0=false. Default to '0' + example: '0' + + paramCloudStorage: + in: query + name: cloud_storage + schema: + type: integer + required: false + description: | + - Upload the generated PDFs/images to our storage CDN, default to `1`. If you have configured `Post Action` to upload the PDFs/Images to your own S3, please set it to `0`. + example: '1' + + paramGenerationDelay: + in: query + name: generation_delay + schema: + type: int + required: false + description: | + Delay in milliseconds before PDF/image generation + + paramPOSTACTIONS3FILEKEY: + in: query + name: postaction_s3_filekey + schema: + type: string + required: false + description: | + - This is to specify the file name for `Post Action(AWS S3/Cloudflare R2/Azure Storage)`. + - Please do not specify the file extension + - Please make sure the file name is unique + - You might use slash (/) as the folder delimiter + - It's default to transaction_ref + + paramPOSTACTIONS3BUCKET: + in: query + name: postaction_s3_bucket + schema: + type: string + required: false + description: | + - This is to overwrite the AWS Bucket for `Post Action(AWS S3/Cloudflare R2 Storage)` or the container for `Post Action(Azure Storage)`. + + + paramMeta: + in: query + name: meta + schema: + type: string + required: false + description: | + - Specify an external reference ID for your own reference. It appears in the `list-objects` API. + example: 'inv-iwj343jospig' + + paramAsync: + in: query + name: async + schema: + type: string + required: false + description: | + - Either `1` or `0`(Default). `0` is synchronous call(default), `1` is asynchronous call + - To generate PDF asynchronously, set the value to `1` and the API call returns immediately. Once the PDF document is generated, we will make a HTTP/HTTPS GET to your URL(webhook_url) and will retry for 3 times before giving up. + - If `async` is set to `1`, then `webhook_url` is mandatory + example: '0' + + paramWebhook: + in: query + name: webhook_url + schema: + type: string + required: false + description: | + - It is the URL of your webhook URL, it starts with http:// or https:// and has to be urlencoded. + - If `async` is set to `1`, then you have to specify the `webhook_url`. + + + #### Format of Webhook callback + + Once the PDF is generated, we will initiate a HTTP/HTTPS GET call to the following URL: + + https://`[yourwebserver.com]`?&primary_url=`[primary_url]`&transaction_ref=`[transaction_ref]`&status=`[status]`&message=`[message]` + + - `[yourwebserver.com]`: The web services to handle the callback, which is the `webhook_url` + - `[primary_url]`: The URL to the PDF document + - `[transaction_ref]`: The transaction reference number + - `[status]` : Status of the transaction, either `success` or `error` + - `[message]` : Status message + + ***The following is a sample webhook call back to your server*** + + https://yourwebserver.com?&primary_url=https%3A%2F%2Fsummer-heart-0930.chufeiyun1688.workers.dev%3A443%2Fhttps%2Fpub-cdn.apitemplate.io%2F2021%2F06%2Fb692183d-46d7-3213-891a-460a5814ad3f.pdf&transaction_ref=b692183d-46d7-3213-891a-460a5814ad3f&status=success + + example: https://yourwebserver.com + + paramWebhookMethod: + in: query + name: webhook_method + schema: + type: string + required: false + description: | + - The HTTP method of the webhook, either `POST` or `GET`. Default to `GET` + example: GET + + paramWebhookHeaders: + in: query + name: webhook_headers + schema: + type: string + required: false + description: | + - The HTTP headers of the webhook, it should be a base64 encoded JSON object. + - The following is an example of base64 encoded JSON: + ```json + eyJ3b3JrZmxvdy1hcGkta2V5Ijoia2V5X0VLc3MxNWJKRXFBMkRHYzM4bkNXNzlaRER1ZUZJeiJ9 + ``` + + The JSON object in clear text for the above base64 encoded JSON: + ```json + { + "workflow-api-key": "key_EKss15bJEqA2DGc38nCW79ZDDueFIz" + } + ``` + example: eyJ3b3JrZmxvdy1hcGkta2V5Ijoia2V5X0VLc3MxNWJKRXFBMkRHYzM4bkNXNzlaRER1ZUZJeiJ9 From 4bd0db52fb6635be8b27d4f95d58c222afd19080 Mon Sep 17 00:00:00 2001 From: Thomasr Date: Wed, 27 Nov 2024 11:13:00 -0500 Subject: [PATCH 34/36] Add fallback to npm repo config when anonymous user --- .../api/npm/PrivateNpmRegistryController.java | 52 +++++++++++++------ 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/npm/PrivateNpmRegistryController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/npm/PrivateNpmRegistryController.java index 2f3614b84..9e967605c 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/npm/PrivateNpmRegistryController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/npm/PrivateNpmRegistryController.java @@ -4,6 +4,8 @@ import org.jetbrains.annotations.NotNull; import org.lowcoder.api.home.SessionUserService; import org.lowcoder.domain.application.service.ApplicationServiceImpl; +import org.lowcoder.domain.organization.model.OrgMember; +import org.lowcoder.domain.organization.model.Organization; import org.lowcoder.domain.organization.service.OrgMemberServiceImpl; import org.lowcoder.domain.organization.service.OrganizationService; import org.lowcoder.infra.constant.NewUrl; @@ -49,23 +51,39 @@ private Mono> forwardToNodeService(String applicationId String withoutLeadingSlash = path.startsWith("/") ? path.substring(1) : path; if(applicationId.equals("none")) { - return sessionUserService.getVisitorOrgMemberCache().flatMap(orgMember -> organizationService.getOrgCommonSettings(orgMember.getOrgId()).flatMap(organizationCommonSettings -> { - Map config = Map.of("npmRegistries", Objects.requireNonNullElse(organizationCommonSettings.get("npmRegistries"), new ArrayList<>(0)), "workspaceId", orgMember.getOrgId()); - return WebClientBuildHelper.builder() - .systemProxy() - .build() - .post() - .uri(nodeServerHelper.createUri(prefix + "/" + withoutLeadingSlash)) - .contentType(MediaType.APPLICATION_JSON) - .body(BodyInserters.fromValue(config)) - .retrieve().toEntity(Resource.class) - .map(response -> { - return ResponseEntity - .status(response.getStatusCode()) - .headers(response.getHeaders()) - .body(response.getBody()); - }); - })); + return sessionUserService.getVisitorOrgMemberCache() + .onErrorResume(e -> Mono.just(OrgMember.builder().orgId("default").build())) + .flatMap(orgMember -> organizationService.getOrgCommonSettings(orgMember.getOrgId()) + .onErrorResume(e -> { + // Handle errors fetching organization settings and provide defaults + Organization.OrganizationCommonSettings defaultSettings = new Organization.OrganizationCommonSettings(); + defaultSettings.put("npmRegistries", new ArrayList<>(0)); + return Mono.just(defaultSettings); + }) + .flatMap(organizationCommonSettings -> { + Map config = Map.of( + "npmRegistries", Objects.requireNonNullElse( + organizationCommonSettings.get("npmRegistries"), + new ArrayList<>(0) + ), + "workspaceId", orgMember.getOrgId() + ); + return WebClientBuildHelper.builder() + .systemProxy() + .build() + .post() + .uri(nodeServerHelper.createUri(prefix + "/" + withoutLeadingSlash)) + .contentType(MediaType.APPLICATION_JSON) + .body(BodyInserters.fromValue(config)) + .retrieve() + .toEntity(Resource.class) + .map(response -> ResponseEntity + .status(response.getStatusCode()) + .headers(response.getHeaders()) + .body(response.getBody()) + ); + })); + } else{ return applicationServiceImpl.findById(applicationId).flatMap(application -> organizationService.getById(application.getOrganizationId())).flatMap(orgMember -> organizationService.getOrgCommonSettings(orgMember.getId()).flatMap(organizationCommonSettings -> { Map config = Map.of("npmRegistries", Objects.requireNonNullElse(organizationCommonSettings.get("npmRegistries"), new ArrayList<>(0)), "workspaceId", orgMember.getId()); From bb7fe209995af437a23969511986719013fe5b49 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Thu, 28 Nov 2024 00:39:41 +0500 Subject: [PATCH 35/36] fixed loading indicator --- .../src/components/NpmRegistryConfig.tsx | 2 +- .../src/comps/comps/fileComp/fileComp.tsx | 2 +- .../comps/mediaComp/colorPickerConstants.tsx | 2 +- client/packages/lowcoder/src/index.ts | 9 --------- .../packages/lowcoder/src/util/hideLoading.tsx | 17 ++++++++++++----- 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/client/packages/lowcoder/src/components/NpmRegistryConfig.tsx b/client/packages/lowcoder/src/components/NpmRegistryConfig.tsx index 6dadfed36..41e035bfd 100644 --- a/client/packages/lowcoder/src/components/NpmRegistryConfig.tsx +++ b/client/packages/lowcoder/src/components/NpmRegistryConfig.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import { HelpText } from "./HelpText"; import { FormInputItem, FormSelectItem, TacoSwitch } from "lowcoder-design"; import { Form } from "antd"; -import { trans } from "@lowcoder-ee/i18n"; +import { trans } from "i18n"; import { FormStyled } from "@lowcoder-ee/pages/setting/idSource/styledComponents"; import { SaveButton } from "@lowcoder-ee/pages/setting/styled"; import { NpmRegistryConfigEntry } from "@lowcoder-ee/redux/reducers/uiReducers/commonSettingsReducer"; diff --git a/client/packages/lowcoder/src/comps/comps/fileComp/fileComp.tsx b/client/packages/lowcoder/src/comps/comps/fileComp/fileComp.tsx index def8c27e6..d46f9ad0b 100644 --- a/client/packages/lowcoder/src/comps/comps/fileComp/fileComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/fileComp/fileComp.tsx @@ -48,7 +48,7 @@ import type { ItemType } from "antd/es/menu/interface"; import Skeleton from "antd/es/skeleton"; import Menu from "antd/es/menu"; import Flex from "antd/es/flex"; -import { checkIsMobile } from "@lowcoder-ee/index.sdk"; +import { checkIsMobile } from "@lowcoder-ee/util/commonUtils"; const FileSizeControl = codeControl((value) => { if (typeof value === "number") { diff --git a/client/packages/lowcoder/src/comps/comps/mediaComp/colorPickerConstants.tsx b/client/packages/lowcoder/src/comps/comps/mediaComp/colorPickerConstants.tsx index cb242546e..c240d2366 100644 --- a/client/packages/lowcoder/src/comps/comps/mediaComp/colorPickerConstants.tsx +++ b/client/packages/lowcoder/src/comps/comps/mediaComp/colorPickerConstants.tsx @@ -1,4 +1,4 @@ -import { trans } from "@lowcoder-ee/i18n"; +import { trans } from "i18n"; export const presets = { diff --git a/client/packages/lowcoder/src/index.ts b/client/packages/lowcoder/src/index.ts index 2072fc849..55906f99c 100644 --- a/client/packages/lowcoder/src/index.ts +++ b/client/packages/lowcoder/src/index.ts @@ -24,15 +24,6 @@ if (!window.ResizeObserver) { window.ResizeObserver = ResizeObserver; } -export function hideLoading() { - // hide loading - const node = document.getElementById("loading"); - if (node) { - // @ts-ignore - node.style.opacity = 0; - } -} - debug(`REACT_APP_EDITION: ${REACT_APP_EDITION}`); debug(`REACT_APP_LANGUAGES:, ${REACT_APP_LANGUAGES}`); debug(`REACT_APP_API_SERVICE_URL:, ${REACT_APP_API_SERVICE_URL}`); diff --git a/client/packages/lowcoder/src/util/hideLoading.tsx b/client/packages/lowcoder/src/util/hideLoading.tsx index f4c12c345..21ed7f34e 100644 --- a/client/packages/lowcoder/src/util/hideLoading.tsx +++ b/client/packages/lowcoder/src/util/hideLoading.tsx @@ -1,10 +1,17 @@ import {useEffect} from "react"; -import {hideLoading} from "@lowcoder-ee/index"; +function hideLoading() { + // hide loading + const node = document.getElementById("loading"); + if (node) { + // @ts-ignore + node.style.opacity = 0; + } +} export const LoadingBarHideTrigger = function(props: any) { - useEffect(() => { - setTimeout(() => hideLoading(), 300); - }, []); + useEffect(() => { + setTimeout(() => hideLoading(), 300); + }, []); - return <> + return <> }; \ No newline at end of file From bfe7a4c49fb6093ff23a701ce6148658bc62cd4e Mon Sep 17 00:00:00 2001 From: Thomasr Date: Thu, 28 Nov 2024 01:35:08 -0500 Subject: [PATCH 36/36] total count fix for organization member --- .../java/org/lowcoder/api/usermanagement/OrgApiServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgApiServiceImpl.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgApiServiceImpl.java index d6aa34203..1b1036c24 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgApiServiceImpl.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgApiServiceImpl.java @@ -85,7 +85,7 @@ public Mono getOrganizationMembers(String orgId, int page, in } private Mono getOrgMemberListView(String orgId, int page, int count) { - return orgMemberService.getOrganizationMembers(orgId, page, count) + return orgMemberService.getOrganizationMembers(orgId) .collectList() .flatMap(orgMembers -> { List userIds = orgMembers.stream()