From c5f4d236fa848f6f28fda3b9669c3669d61250b7 Mon Sep 17 00:00:00 2001 From: fanyinbo <1553199396@qq.com> Date: Tue, 25 Apr 2023 14:38:35 +0800 Subject: [PATCH 01/37] Optimize the export and remove the id suffix after the file Signed-off-by: fanyinbo <1553199396@qq.com> --- .../src/main/java/ai/basic/x1/usecase/ExportUseCase.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/ai/basic/x1/usecase/ExportUseCase.java b/backend/src/main/java/ai/basic/x1/usecase/ExportUseCase.java index 93ba98ba..ca34b0bb 100644 --- a/backend/src/main/java/ai/basic/x1/usecase/ExportUseCase.java +++ b/backend/src/main/java/ai/basic/x1/usecase/ExportUseCase.java @@ -183,11 +183,11 @@ private void writeFile(List list, String zipPath, logger.error("Download object error", e); } } - var dataPath = String.format("%s/%s/%s-%s%s", zipPath, Constants.DATA, dataExportBaseBO.getName(), dataExportBaseBO.getId(), ".json"); + var dataPath = String.format("%s/%s/%s%s", zipPath, Constants.DATA, dataExportBaseBO.getName(), ".json"); FileUtil.writeString(JSONUtil.toJsonStr(dataExportBaseBO, jsonConfig), dataPath, StandardCharsets.UTF_8); if (ObjectUtil.isNotNull(dataExportBO.getResult())) { - var resultPath = String.format("%s/%s/%s-%s%s", zipPath, Constants.RESULT, - dataExportBaseBO.getName(), dataExportBaseBO.getId(), ".json"); + var resultPath = String.format("%s/%s/%s%s", zipPath, Constants.RESULT, + dataExportBaseBO.getName(), ".json"); FileUtil.writeString(JSONUtil.toJsonStr(dataExportBO.getResult(), jsonConfig), resultPath, StandardCharsets.UTF_8); } }); From fe7c65783fb41d0d1fbd18ace33eb9f002cd4daa Mon Sep 17 00:00:00 2001 From: fanyinbo <1553199396@qq.com> Date: Tue, 25 Apr 2023 16:02:12 +0800 Subject: [PATCH 02/37] Update xtreme1-sdk version Signed-off-by: fanyinbo <1553199396@qq.com> --- .ops/backend.dockerfile | 2 +- backend/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.ops/backend.dockerfile b/.ops/backend.dockerfile index 77aca33a..06dd9b42 100644 --- a/.ops/backend.dockerfile +++ b/.ops/backend.dockerfile @@ -2,7 +2,7 @@ FROM openjdk:11 RUN apt update && \ apt install -y iputils-ping curl wget netcat python3 python3-pip -RUN pip3 install --upgrade --force-reinstall git+https://github.com/xtreme1-io/xtreme1-sdk.git@bddaa8d +RUN pip3 install --upgrade --force-reinstall git+https://github.com/xtreme1-io/xtreme1-sdk.git@d0cf4cc WORKDIR /app COPY target/$BACKEND_PACKAGE_NAME ./app.jar RUN mkdir -p config diff --git a/backend/Dockerfile b/backend/Dockerfile index ac2db101..c7d4ef94 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -7,7 +7,7 @@ RUN --mount=type=cache,target=/root/.m2 mvn package FROM openjdk:11-jre RUN apt update && \ apt install -y iputils-ping curl wget netcat python3 python3-pip git -RUN pip3 install --upgrade --force-reinstall git+https://github.com/xtreme1-io/xtreme1-sdk.git@bddaa8d +RUN pip3 install --upgrade --force-reinstall git+https://github.com/xtreme1-io/xtreme1-sdk.git@d0cf4cc WORKDIR /app COPY --from=build /build/target/xtreme1-backend-0.6.1-SNAPSHOT.jar ./app.jar RUN mkdir -p config From 69b092fe2210c087eae3fabb2f9740749e9899e4 Mon Sep 17 00:00:00 2001 From: fanyinbo <1553199396@qq.com> Date: Wed, 26 Apr 2023 14:19:22 +0800 Subject: [PATCH 03/37] Text annotation upload Signed-off-by: fanyinbo <1553199396@qq.com> --- backend/src/main/java/ai/basic/x1/entity/TextDataContentBO.java | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 backend/src/main/java/ai/basic/x1/entity/TextDataContentBO.java diff --git a/backend/src/main/java/ai/basic/x1/entity/TextDataContentBO.java b/backend/src/main/java/ai/basic/x1/entity/TextDataContentBO.java new file mode 100644 index 00000000..d8942b23 --- /dev/null +++ b/backend/src/main/java/ai/basic/x1/entity/TextDataContentBO.java @@ -0,0 +1,2 @@ +package ai.basic.x1.usecase;public class TextDataContentBO { +} From 5e1fa52ccc3908607b7800c851f795442602a65d Mon Sep 17 00:00:00 2001 From: fanyinbo <1553199396@qq.com> Date: Wed, 26 Apr 2023 14:19:39 +0800 Subject: [PATCH 04/37] Text annotation upload Signed-off-by: fanyinbo <1553199396@qq.com> --- .../dto/request/DatasetRequestDTO.java | 2 +- .../ai/basic/x1/entity/TextDataContentBO.java | 21 ++- .../x1/entity/enums/DatasetTypeEnum.java | 6 +- .../ai/basic/x1/usecase/DataInfoUseCase.java | 160 ++++++++++++++++-- .../main/java/ai/basic/x1/util/Constants.java | 5 + 5 files changed, 181 insertions(+), 13 deletions(-) diff --git a/backend/src/main/java/ai/basic/x1/adapter/dto/request/DatasetRequestDTO.java b/backend/src/main/java/ai/basic/x1/adapter/dto/request/DatasetRequestDTO.java index 585db8de..46d31a48 100644 --- a/backend/src/main/java/ai/basic/x1/adapter/dto/request/DatasetRequestDTO.java +++ b/backend/src/main/java/ai/basic/x1/adapter/dto/request/DatasetRequestDTO.java @@ -34,7 +34,7 @@ public class DatasetRequestDTO { * Dataset type LIDAR_FUSION, LIDAR_BASIC, IMAGE */ @NotEmpty(message = "dataset type cannot be null", groups = GroupInsert.class) - @ValidStringEnum(message = "dataset type must be one of LIDAR_FUSION, LIDAR_BASIC, IMAGE", enumClass = DatasetTypeEnum.class) + @ValidStringEnum(message = "dataset type must be one of LIDAR_FUSION, LIDAR_BASIC, IMAGE,TEXT", enumClass = DatasetTypeEnum.class) private String type; /** diff --git a/backend/src/main/java/ai/basic/x1/entity/TextDataContentBO.java b/backend/src/main/java/ai/basic/x1/entity/TextDataContentBO.java index d8942b23..cfbd50b2 100644 --- a/backend/src/main/java/ai/basic/x1/entity/TextDataContentBO.java +++ b/backend/src/main/java/ai/basic/x1/entity/TextDataContentBO.java @@ -1,2 +1,21 @@ -package ai.basic.x1.usecase;public class TextDataContentBO { +package ai.basic.x1.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TextDataContentBO { + + private String id; + + private String parentId; + + private String text; + + private String role; } diff --git a/backend/src/main/java/ai/basic/x1/entity/enums/DatasetTypeEnum.java b/backend/src/main/java/ai/basic/x1/entity/enums/DatasetTypeEnum.java index 09395786..baf31021 100644 --- a/backend/src/main/java/ai/basic/x1/entity/enums/DatasetTypeEnum.java +++ b/backend/src/main/java/ai/basic/x1/entity/enums/DatasetTypeEnum.java @@ -17,5 +17,9 @@ public enum DatasetTypeEnum { /** * IMAGE */ - IMAGE + IMAGE, + /** + * TEXT + */ + TEXT } diff --git a/backend/src/main/java/ai/basic/x1/usecase/DataInfoUseCase.java b/backend/src/main/java/ai/basic/x1/usecase/DataInfoUseCase.java index b8ee45bf..77562875 100644 --- a/backend/src/main/java/ai/basic/x1/usecase/DataInfoUseCase.java +++ b/backend/src/main/java/ai/basic/x1/usecase/DataInfoUseCase.java @@ -3,7 +3,6 @@ import ai.basic.x1.adapter.api.config.DatasetInitialInfo; import ai.basic.x1.adapter.api.context.RequestContextHolder; import ai.basic.x1.adapter.dto.ApiResult; -import ai.basic.x1.adapter.dto.DatasetClassDTO; import ai.basic.x1.adapter.port.dao.*; import ai.basic.x1.adapter.port.dao.mybatis.extension.ExtendLambdaQueryWrapper; import ai.basic.x1.adapter.port.dao.mybatis.model.DataInfo; @@ -18,7 +17,6 @@ import ai.basic.x1.usecase.exception.UsecaseCode; import ai.basic.x1.usecase.exception.UsecaseException; import ai.basic.x1.util.*; -import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.ListUtil; @@ -29,6 +27,8 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.StreamProgress; import cn.hutool.core.lang.UUID; +import cn.hutool.core.lang.tree.Tree; +import cn.hutool.core.lang.tree.TreeUtil; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.*; import cn.hutool.crypto.SecureUtil; @@ -47,15 +47,18 @@ import org.springframework.dao.DuplicateKeyException; import org.springframework.transaction.annotation.Transactional; -import java.io.*; import java.io.File; +import java.io.FileFilter; +import java.io.IOException; import java.math.BigDecimal; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.time.OffsetDateTime; import java.util.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -63,6 +66,7 @@ import static ai.basic.x1.entity.enums.DataUploadSourceEnum.LOCAL; import static ai.basic.x1.entity.enums.DatasetTypeEnum.IMAGE; import static ai.basic.x1.entity.enums.DatasetTypeEnum.*; +import static ai.basic.x1.entity.enums.DatasetTypeEnum.TEXT; import static ai.basic.x1.entity.enums.RelationEnum.*; import static ai.basic.x1.entity.enums.SplitTypeEnum.NOT_SPLIT; import static ai.basic.x1.entity.enums.UploadStatusEnum.*; @@ -185,11 +189,17 @@ public class DataInfoUseCase { /** * Filter out files whose file suffix is not image, and discard the file when it returns false */ - private final FileFilter filefilter = file -> { + private final FileFilter imageFileFilter = file -> { //if the file extension is image return true, else false - return IMAGE_DATA_TYPE.contains(FileUtil.getMimeType(file.getAbsolutePath())) && Constants.IMAGE.equals(FileUtil.getName(file.getParentFile())); + return IMAGE_DATA_TYPE.contains(FileUtil.getMimeType(file.getAbsolutePath())) && Constants.IMAGE.equalsIgnoreCase(FileUtil.getName(file.getParentFile())); }; + private final FileFilter textFileFilter = file -> { + //if the file extension is json return true, else false + return file.getAbsolutePath().toUpperCase().endsWith(JSON_SUFFIX) && Constants.TEXT.equalsIgnoreCase(FileUtil.getName(file.getParentFile())); + }; + + /** * Data split * @@ -485,7 +495,9 @@ public List insertBatch(List dataInfoBOList, Long datase if (CollUtil.isNotEmpty(existDataInfoList)) { var existNames = existDataInfoList.stream().map(DataInfoBO::getName).collect(Collectors.toList()); dataInfoBOList = dataInfoBOList.stream().filter(dataInfoBO -> !existNames.contains(dataInfoBO.getName())).collect(Collectors.toList()); - errorBuilder.append("Duplicate data names;"); + if (!errorBuilder.toString().contains("Duplicate")) { + errorBuilder.append("Duplicate data names;"); + } } if (CollUtil.isEmpty(dataInfoBOList)) { return List.of(); @@ -535,11 +547,13 @@ public Long upload(DataInfoUploadBO dataInfoUploadBO) { executorService.execute(Objects.requireNonNull(TtlRunnable.get(() -> { try { if (IMAGE.equals(dataset.getType()) && IMAGE_DATA_TYPE.contains(mimeType)) { - downloadAndDecompressionFile(dataInfoUploadBO, this::parseImageUploadFile); + this.downloadAndDecompressionFile(dataInfoUploadBO, this::parseImageUploadFile); } else if (IMAGE.equals(dataset.getType()) && COMPRESSED_DATA_TYPE.contains(mimeType)) { - downloadAndDecompressionFile(dataInfoUploadBO, this::parseImageCompressedUploadFile); + this.downloadAndDecompressionFile(dataInfoUploadBO, this::parseImageCompressedUploadFile); + } else if (TEXT.equals(dataset.getType())) { + this.downloadAndDecompressionFile(dataInfoUploadBO, this::parseTextUploadFile); } else { - downloadAndDecompressionFile(dataInfoUploadBO, this::parsePointCloudUploadFile); + this.downloadAndDecompressionFile(dataInfoUploadBO, this::parsePointCloudUploadFile); } } catch (IOException e) { log.error("Download decompression file error", e); @@ -746,6 +760,80 @@ private void parseImageUploadFile(DataInfoUploadBO dataInfoUploadBO) { datasetSimilarityJobUseCase.submitJob(datasetId); } + + public void parseTextUploadFile(DataInfoUploadBO dataInfoUploadBO) { + var userId = dataInfoUploadBO.getUserId(); + var datasetId = dataInfoUploadBO.getDatasetId(); + var files = FileUtil.loopFiles(Paths.get(dataInfoUploadBO.getBaseSavePath()), 10, textFileFilter); + var rootPath = String.format("%s/%s", userId, datasetId); + var errorBuilder = new StringBuilder(); + var dataInfoBOBuilder = DataInfoBO.builder().datasetId(datasetId).status(DataStatusEnum.VALID) + .annotationStatus(DataAnnotationStatusEnum.NOT_ANNOTATED) + .createdAt(OffsetDateTime.now()) + .updatedAt(OffsetDateTime.now()) + .createdBy(userId) + .isDeleted(false); + var totalDataNum = Long.valueOf(files.size()); + AtomicReference parsedDataNum = new AtomicReference<>(0L); + var uploadRecordBOBuilder = UploadRecordBO.builder() + .id(dataInfoUploadBO.getUploadRecordId()).totalDataNum(totalDataNum).parsedDataNum(parsedDataNum.get()).status(PARSING); + if (CollectionUtil.isNotEmpty(files)) { + CountDownLatch countDownLatch = new CountDownLatch(files.size()); + files.forEach(f -> parseExecutorService.submit(Objects.requireNonNull(TtlRunnable.get(() -> { + try { + var dataInfoBOList = new ArrayList(); + var textJson = JSONUtil.readJSONArray(f, StandardCharsets.UTF_8); + var list = JSONUtil.toList(textJson.toString(), TextDataContentBO.class); + var pathList = this.getTreeAllPath(list); + var newTextFileList = new ArrayList(); + AtomicInteger i = new AtomicInteger(1); + pathList.forEach(path -> { + ListUtil.reverse(path); + var suffix = FileUtil.getSuffix(f); + var originalPath = f.getAbsolutePath(); + var newPath = String.format("%s_%s.%s", StrUtil.removeSuffix(originalPath, String.format(".%s", suffix)), i.get(), suffix); + var file = FileUtil.writeString(JSONUtil.toJsonStr(path), newPath, StandardCharsets.UTF_8); + newTextFileList.add(file); + i.getAndIncrement(); + }); + var fileBOS = uploadFileList(rootPath, newTextFileList, dataInfoUploadBO); + createUploadThumbnail(userId, fileBOS, rootPath); + fileBOS.forEach(fileBO -> { + var tempDataId = ByteUtil.bytesToLong(SecureUtil.md5().digest(UUID.randomUUID().toString())); + var file = FileUtil.file(tempPath + fileBO.getPath().replace(rootPath, "")); + var fileNodeBO = DataInfoBO.FileNodeBO.builder().name(fileBO.getName()) + .fileId(fileBO.getId()).type(FILE).build(); + var dataInfoBO = dataInfoBOBuilder.name(getFileName(file)).content(Collections.singletonList(fileNodeBO)).splitType(NOT_SPLIT).tempDataId(tempDataId).build(); + dataInfoBOList.add(dataInfoBO); + }); + if (CollectionUtil.isNotEmpty(dataInfoBOList)) { + insertBatch(dataInfoBOList, datasetId, errorBuilder); + } + } catch (Exception e) { + log.error("Handle data error", e); + } finally { + parsedDataNum.set(parsedDataNum.get() + 1); + var uploadRecordBO = uploadRecordBOBuilder.parsedDataNum(parsedDataNum.get()).build(); + uploadRecordDAO.updateById(DefaultConverter.convert(uploadRecordBO, UploadRecord.class)); + countDownLatch.countDown(); + } + + })))); + try { + countDownLatch.await(); + } catch (InterruptedException e) { + log.error("Parse image count down latch error", e); + } + var uploadRecordBO = uploadRecordBOBuilder.parsedDataNum(totalDataNum).errorMessage(errorBuilder.toString()).status(PARSE_COMPLETED).build(); + uploadRecordDAO.updateById(DefaultConverter.convert(uploadRecordBO, UploadRecord.class)); + datasetSimilarityJobUseCase.submitJob(datasetId); + } else { + var uploadRecordBO = uploadRecordBOBuilder.status(FAILED).errorMessage(COMPRESSED_PACKAGE_EMPTY.getMessage()).build(); + uploadRecordDAO.updateById(DefaultConverter.convert(uploadRecordBO, UploadRecord.class)); + log.error("Image compressed package is empty,dataset id:{},filePath:{}", datasetId, dataInfoUploadBO.getFileUrl()); + } + } + public void parseImageCompressedUploadFile(DataInfoUploadBO dataInfoUploadBO) { if (DataFormatEnum.COCO.equals(dataInfoUploadBO.getDataFormat())) { var respPath = cocoConvertToX1(dataInfoUploadBO); @@ -762,7 +850,7 @@ public void parseImageCompressedUploadFile(DataInfoUploadBO dataInfoUploadBO) { } var userId = dataInfoUploadBO.getUserId(); var datasetId = dataInfoUploadBO.getDatasetId(); - var files = FileUtil.loopFiles(Paths.get(dataInfoUploadBO.getBaseSavePath()), 3, filefilter); + var files = FileUtil.loopFiles(Paths.get(dataInfoUploadBO.getBaseSavePath()), 3, imageFileFilter); var rootPath = String.format("%s/%s", userId, datasetId); var dataAnnotationObjectBOBuilder = DataAnnotationObjectBO.builder() .datasetId(datasetId).createdBy(userId).createdAt(OffsetDateTime.now()); @@ -2038,4 +2126,56 @@ public DataInfoBO getInitDataInfoBO(DatasetInitialInfo datasetInitialInfo) { return dataInfoBO; } + /** + * Get all paths in the tree list + * + * @param list tree list + * @return + */ + private List> getTreeAllPath(List list) { + //转换器 + List> treeNodes = TreeUtil.build(list, null, + (treeNode, tree) -> { + tree.setId(treeNode.getId()); + tree.setParentId(treeNode.getParentId()); + tree.setName(treeNode.getId()); + // 扩展属性 ... + tree.putExtra("text", treeNode.getText()); + tree.putExtra("role", treeNode.getRole()); + }); + + var leafNodeList = new ArrayList>(); + getLeafNodeList(treeNodes, leafNodeList); + // 获取所有链路 + List> paths = new ArrayList<>(); + for (Tree treeNode : leafNodeList) { + List path = new ArrayList<>(); + path.add(DefaultConverter.convert(treeNode, TextDataContentBO.class)); + Tree parent = treeNode.getParent(); + while (parent != null) { + if (ObjectUtil.isNotNull(parent.getId())) { + path.add(DefaultConverter.convert(parent, TextDataContentBO.class)); + } + parent = parent.getParent(); + } + paths.add(path); + } + return paths; + } + + /** + * Get all leaf nodes under the tree + * + * @param treeNodes tree node + * @param leafNodeList collection of leaf nodes + */ + private void getLeafNodeList(List> treeNodes, List> leafNodeList) { + treeNodes.forEach(tree -> { + if (CollUtil.isNotEmpty(tree.getChildren())) { + getLeafNodeList(tree.getChildren(), leafNodeList); + } else { + leafNodeList.add(tree); + } + }); + } } diff --git a/backend/src/main/java/ai/basic/x1/util/Constants.java b/backend/src/main/java/ai/basic/x1/util/Constants.java index a3533550..2de27070 100644 --- a/backend/src/main/java/ai/basic/x1/util/Constants.java +++ b/backend/src/main/java/ai/basic/x1/util/Constants.java @@ -79,6 +79,11 @@ public interface Constants { */ String IMAGE = "image"; + /** + * image + */ + String TEXT = "text"; + String RESULT = "result"; String SLANTING_BAR = "/"; From 3044303335636ea81a722bbdecbb8a0549fbdcd6 Mon Sep 17 00:00:00 2001 From: fanyinbo <1553199396@qq.com> Date: Wed, 26 Apr 2023 14:52:26 +0800 Subject: [PATCH 05/37] Text annotation export Signed-off-by: fanyinbo <1553199396@qq.com> --- .../ai/basic/x1/entity/TextDataExportBO.java | 24 +++++++++++++++++++ .../ai/basic/x1/usecase/DataInfoUseCase.java | 10 ++++++++ 2 files changed, 34 insertions(+) create mode 100644 backend/src/main/java/ai/basic/x1/entity/TextDataExportBO.java diff --git a/backend/src/main/java/ai/basic/x1/entity/TextDataExportBO.java b/backend/src/main/java/ai/basic/x1/entity/TextDataExportBO.java new file mode 100644 index 00000000..ad993d1b --- /dev/null +++ b/backend/src/main/java/ai/basic/x1/entity/TextDataExportBO.java @@ -0,0 +1,24 @@ +package ai.basic.x1.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TextDataExportBO extends DataExportBaseBO { + + /** + * Text url + */ + private String textUrl; + + /** + * The path in the compressed package + */ + private String textZipPath; + +} diff --git a/backend/src/main/java/ai/basic/x1/usecase/DataInfoUseCase.java b/backend/src/main/java/ai/basic/x1/usecase/DataInfoUseCase.java index 77562875..04bf2b9d 100644 --- a/backend/src/main/java/ai/basic/x1/usecase/DataInfoUseCase.java +++ b/backend/src/main/java/ai/basic/x1/usecase/DataInfoUseCase.java @@ -1748,6 +1748,8 @@ private DataExportBaseBO assembleExportDataContent(DataInfoBO dataInfoBO, Datase String pointCloudZipPath = null; String cameraConfigUrl = null; String cameraConfigZipPath = null; + String textUrl = null; + String textZipPath = null; var images = new ArrayList(); for (DataInfoBO.FileNodeBO f : dataInfoBO.getContent()) { var relationFileBO = FILE.equals(f.getType()) ? f.getFile() : CollectionUtil.getFirst(f.getFiles()).getFile(); @@ -1757,6 +1759,9 @@ private DataExportBaseBO assembleExportDataContent(DataInfoBO dataInfoBO, Datase } else if (f.getName().equals(StrUtil.toCamelCase(CAMERA_CONFIG))) { cameraConfigUrl = relationFileBO.getUrl(); cameraConfigZipPath = relationFileBO.getZipPath(); + } else if (f.getName().toUpperCase().endsWith(JSON_SUFFIX) && TEXT.equals(datasetType)) { + textUrl = relationFileBO.getUrl(); + textZipPath = relationFileBO.getZipPath(); } else { var url = relationFileBO.getUrl(); var zipPath = relationFileBO.getZipPath(); @@ -1791,6 +1796,11 @@ private DataExportBaseBO assembleExportDataContent(DataInfoBO dataInfoBO, Datase ((ImageDataExportBO) dataExportBaseBO).setHeight(image.getHeight()); ((ImageDataExportBO) dataExportBaseBO).setFilePath(image.getFilePath()); break; + case TEXT: + dataExportBaseBO = DefaultConverter.convert(dataExportBaseBO, TextDataExportBO.class); + ((TextDataExportBO) dataExportBaseBO).setTextUrl(textUrl); + ((TextDataExportBO) dataExportBaseBO).setTextZipPath(textZipPath); + break; default: break; } From f359722b5a80738dedc1e4a3b74102ebb7831046 Mon Sep 17 00:00:00 2001 From: fanyinbo <1553199396@qq.com> Date: Wed, 26 Apr 2023 15:07:12 +0800 Subject: [PATCH 06/37] Text annotation upload data verification Signed-off-by: fanyinbo <1553199396@qq.com> --- .../src/main/java/ai/basic/x1/usecase/DataInfoUseCase.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/backend/src/main/java/ai/basic/x1/usecase/DataInfoUseCase.java b/backend/src/main/java/ai/basic/x1/usecase/DataInfoUseCase.java index 04bf2b9d..42fd3044 100644 --- a/backend/src/main/java/ai/basic/x1/usecase/DataInfoUseCase.java +++ b/backend/src/main/java/ai/basic/x1/usecase/DataInfoUseCase.java @@ -785,6 +785,9 @@ public void parseTextUploadFile(DataInfoUploadBO dataInfoUploadBO) { var textJson = JSONUtil.readJSONArray(f, StandardCharsets.UTF_8); var list = JSONUtil.toList(textJson.toString(), TextDataContentBO.class); var pathList = this.getTreeAllPath(list); + if (CollUtil.isEmpty(pathList)) { + return; + } var newTextFileList = new ArrayList(); AtomicInteger i = new AtomicInteger(1); pathList.forEach(path -> { @@ -2143,6 +2146,10 @@ public DataInfoBO getInitDataInfoBO(DatasetInitialInfo datasetInitialInfo) { * @return */ private List> getTreeAllPath(List list) { + list = list.stream().filter(t -> StrUtil.isNotEmpty(t.getId()) && StrUtil.isNotEmpty(t.getRole()) && StrUtil.isNotEmpty(t.getText())).collect(Collectors.toList()); + if (CollUtil.isEmpty(list)) { + return List.of(); + } //转换器 List> treeNodes = TreeUtil.build(list, null, (treeNode, tree) -> { From a048f4c9920c32a4c94edd39e69b1476d3700c2e Mon Sep 17 00:00:00 2001 From: fanyinbo <1553199396@qq.com> Date: Wed, 26 Apr 2023 15:09:18 +0800 Subject: [PATCH 07/37] Text annotation upload data verification Signed-off-by: fanyinbo <1553199396@qq.com> --- .../src/main/java/ai/basic/x1/usecase/DataInfoUseCase.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/ai/basic/x1/usecase/DataInfoUseCase.java b/backend/src/main/java/ai/basic/x1/usecase/DataInfoUseCase.java index 42fd3044..c96dd014 100644 --- a/backend/src/main/java/ai/basic/x1/usecase/DataInfoUseCase.java +++ b/backend/src/main/java/ai/basic/x1/usecase/DataInfoUseCase.java @@ -2150,7 +2150,7 @@ private List> getTreeAllPath(List lis if (CollUtil.isEmpty(list)) { return List.of(); } - //转换器 + // convert to tree List> treeNodes = TreeUtil.build(list, null, (treeNode, tree) -> { tree.setId(treeNode.getId()); @@ -2163,7 +2163,7 @@ private List> getTreeAllPath(List lis var leafNodeList = new ArrayList>(); getLeafNodeList(treeNodes, leafNodeList); - // 获取所有链路 + // get all links List> paths = new ArrayList<>(); for (Tree treeNode : leafNodeList) { List path = new ArrayList<>(); From fc234d96b3b89931afad0d57b7afe82dfdb6aa0b Mon Sep 17 00:00:00 2001 From: fanyinbo <1553199396@qq.com> Date: Wed, 26 Apr 2023 15:10:18 +0800 Subject: [PATCH 08/37] Optimize upload Signed-off-by: fanyinbo <1553199396@qq.com> --- backend/src/main/java/ai/basic/x1/usecase/DataInfoUseCase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/ai/basic/x1/usecase/DataInfoUseCase.java b/backend/src/main/java/ai/basic/x1/usecase/DataInfoUseCase.java index c96dd014..a665d4cb 100644 --- a/backend/src/main/java/ai/basic/x1/usecase/DataInfoUseCase.java +++ b/backend/src/main/java/ai/basic/x1/usecase/DataInfoUseCase.java @@ -2156,7 +2156,7 @@ private List> getTreeAllPath(List lis tree.setId(treeNode.getId()); tree.setParentId(treeNode.getParentId()); tree.setName(treeNode.getId()); - // 扩展属性 ... + // Extended properties ... tree.putExtra("text", treeNode.getText()); tree.putExtra("role", treeNode.getRole()); }); From 702f167b28de97a6ec87dd0dc00c1527c60d69d8 Mon Sep 17 00:00:00 2001 From: liujian Date: Wed, 26 Apr 2023 15:15:57 +0800 Subject: [PATCH 09/37] feat: text dataset Signed-off-by: liujian --- .../src/api/business/model/classesModel.ts | 1 + .../src/api/business/model/datasetModel.ts | 1 + .../src/assets/images/dataset/textEmpty.png | Bin 0 -> 70613 bytes .../src/assets/images/dataset/textImg.png | Bin 0 -> 4991 bytes .../src/assets/images/dataset/text_type.png | Bin 0 -> 1768 bytes .../BasicCustom/CustomRadio/index.vue | 4 +- .../datasetClass/components/formSchemas.tsx | 1 + .../datasetClass/components/typing.ts | 1 + .../datasetContent/components/ExportModal.vue | 2 +- .../datasetContent/components/ImgCard.vue | 194 +++++++++++++----- .../datasetContent/components/Tools.vue | 21 +- .../components/upload/UploadContent.vue | 2 +- .../components/useCardObject.ts | 18 +- .../views/datasets/datasetContent/index.vue | 16 +- .../components/CreateDatasetForm.vue | 6 + .../components/DatasetListCard.vue | 17 +- .../components/createDatasetForm.less | 4 +- .../src/views/datasets/datasetList/index.vue | 1 + .../datasetOntology/Classification.vue | 22 +- .../datasetOntology/components/Action.vue | 4 +- .../datasetOntology/components/actionList.ts | 21 ++ .../views/ontology/classes/attributes/data.ts | 1 + 22 files changed, 259 insertions(+), 78 deletions(-) create mode 100644 frontend/main/src/assets/images/dataset/textEmpty.png create mode 100644 frontend/main/src/assets/images/dataset/textImg.png create mode 100644 frontend/main/src/assets/images/dataset/text_type.png diff --git a/frontend/main/src/api/business/model/classesModel.ts b/frontend/main/src/api/business/model/classesModel.ts index f9728b69..97f3c049 100644 --- a/frontend/main/src/api/business/model/classesModel.ts +++ b/frontend/main/src/api/business/model/classesModel.ts @@ -28,6 +28,7 @@ export enum inputTypeEnum { MULTI_SELECTION = 'MULTI_SELECTION', DROPDOWN = 'DROPDOWN', TEXT = 'TEXT', + LONG_TEXT = 'LONG_TEXT', } /** searchForm params */ diff --git a/frontend/main/src/api/business/model/datasetModel.ts b/frontend/main/src/api/business/model/datasetModel.ts index 6eaf4ca6..5ed88572 100644 --- a/frontend/main/src/api/business/model/datasetModel.ts +++ b/frontend/main/src/api/business/model/datasetModel.ts @@ -99,6 +99,7 @@ export enum datasetTypeEnum { LIDAR_BASIC = 'LIDAR_BASIC', IMAGE = 'IMAGE', LIDAR = 'LIDAR', + TEXT = 'TEXT', } export interface fileItem { diff --git a/frontend/main/src/assets/images/dataset/textEmpty.png b/frontend/main/src/assets/images/dataset/textEmpty.png new file mode 100644 index 0000000000000000000000000000000000000000..f9c95198efa20ce02af3bfeca1fc7241ab58a9a1 GIT binary patch literal 70613 zcmdRV^Lu1n&}}E_aAMn>*mlRp#GcsB#L2|AZBA_4wlT4tOgK05-tW8r!~LO8KL_2< z-fLH_T2-}An4-J{0vs+J002Ock_0IO0N_Xf0GK!o)aR9pbgzuhFIam?O(y^Vlj7e8 zOiG#j`tu@~ld^;eplS;L)l)K z;q~!}UCp(7ThH@Xd1a;6w`abjZ&o(1mxA}O!!#wCj${8FR6=n+HX#4sDUF@5{H^`} zy+u>fLBT!R|G&@PChwCY{l5pb;}iJ)_djUtr2c>Ukvoz+_kk|4Y4Y%8*ISpbZCOrf z^%=IAwqCbFh*%rH;CiEPmWz0s+jwX4>tTFCAoXz0#IYL~Z4K3fkr8bs_V5&~MHHdjy`AILNh(9NjsO=aZqLOW;#KaDJwcIK_U4I2Q(GB|2 zx`QJqL4WG0uV$dlCNg{XR5~oH9685(Fxv(s!PV;77YYLuMvD>G=!n;rAdLT8PyJ>` z+f=@TR?IB!YX2Aon$$HTSfhDRk5zzi4Wa}c_r##s)_aTf{6R1&)sm#2Ml>;e5)~=2 zgKhzur$@mwq0-4K)4~+%=|3Cdi^|;JO+3+_di&t(963kp2)zcpD186`4L`x z*EpD@fcaWISC3Ik?jU#8HenCB2iw7Jm%RJkuxIS@M(|Hr5WzLUgR?}8PmNCjMT7?B zEJE@krRZP1+~W}It%XiZb=3M*)~}<>o>_9qg0kx#*!nag`dMFsq#YbNM?m&}PUoe! zK_34m(Uln&qCzP914)Pu3;oX>H=Yat-{WdF@P&{#Iro6!gwp^I4>ilV#qXVZl$KOc z2A6Uiv%NTuY7D6YJdjK|HdXDRzh+nk?YP6N-G0DetAv@9be`MTN1+GlpxvL4!t=yc z(U{^a82sbrgm_*2uuTT^D9&R@@y)-3%3V-<|GPu{nz(9SawPoGN2H3pyV*ZC^*Xm`Hv`p+G6VNVi;yB*%R1?m(GS0#Oa1xnGHs_Z3cQ z=Bn(Iz({8>wm;|{K-bFD?c0>?OY5t_zT4S_7F=Y$PGM1Bj#faT-0 zNjwf&3pAKV!{?|Z9ZQG`9@vfC(wRq1TbW{4HmRhH)=m_@Bq1`>3&>ETtX3K@D-}iN z?pZ^XZ9Mz5l-sFM9Cxk*{{bf`-z5HlN+78U54Cgk(Opgh zn;(E{arp_bpaZ*;#u;w?=Z}Ilz7+(=FcpZ++%5rbE!m%DO&@B%m00s~620xRnBs7m zH)mXPLEBc9?2C#nbYA>=i$ll9p#qg7jhb1pH!E$5B1)*rrz_t4w>qH<=?%@^h&(b? z&Kk(jFNoj+if*mGsQ=u`%$NZqw*Z~C|VpdEw;mweNy_MgD3x63ZTvh)|Nrcj)F z{IVL3c{>?2!x4?InoUDRzp-Q)S}u=SP17dt+@{}_M3AODQLqP~%Th-Wou$l{t9)g{ zrLk7A$Hk0A8ziW3b4!sEvNY056T6P77zV2f^HrK(cb)lPdK2xoLM=ipZj$FdIi>x- z=%C!^zvZD?M+(dF9yO3=n>n)Ahc_PE$LDLaV@N3j5XQz%%^CsaJlm2yZ@_ErVDdZ( zma{U&L8ZlbuI{QvBPsWS#H=7jM1}yy(YP)94Go&6H{xw4UF0`6n#-80)M~^7LqcuO zEjGbGyF_4hlp+rcp?WOFf^^s#Z=sYwlDN zX6%*B!JaM>PC-m;x4EoPFJL5J7=npHRq!}uxw%s4v^edD1dc>p2_*Qa4_pLG>_hn9 zOjwOeRv1f=0XBzj0(w8Vcsr10heP&f(xWzNKshgqU@wz1mUY9G5B(hF({+oWv`;*3 zB*$ZiOB6a#iEC{JCy?td@?$}=Zi~_<$X%HgorfizX+L!a=XLuX_zQ3e;IL$3!-4-G^g+Sp zl+O{D%^?e=gxY63;Y%DiXz5-&<^*X{+j#$tkDL_tVf2vFBHSFQ(GWkywLtQxI`38# zPiylFMHN<<(I&8F3ojm!QAF5$kUH{V<}C;#7SU8)IkBc3h0}=_xHh8u!gHW;-%Cd$ zNcQ*T+uo`)4`D1)PtQK{xB(9!Uy z3V@Xk$$EW#G=cY0Nr;vf>P&k}G7OQ}NdxnG>fA0+wRn!bNNKN279Faq4+Sf#wAv+?>0+v^MaUY-#CJ>aiSxErAMEX2n0$q zi2Ue08cp!bi1F-I_&b6M-Ea}B<$|-9sIoQr&$&Pz4gSkN&Wgk^Y*zfpYu!Y@q#X?E z3;9hwd=&!^tUh#3N;euDfmbM3iGaCg4j`V+r^vyIjnJ!hmE5^ctJ#kzQihL^a^mE1 z!A?ZCe}>YYSi#e5A(7JI`Zd{BxWSuaouzK{$mZrIYa1wxFmpHpy=jFv8z2t+TEm<# zzY)8jUNW~qwevI)ko8l{w~2cWnU>(L)9Ch-d!F0kJ^`&8osE><-cM1d0IvJf z$3_q)?O4S{x9e&0@XTy^eRM>QslKJTs2MI2Ngow&3kx~N@Ox%^syGEMBnY{*YnS~M ztbz9a`>tPW$4_ewyT@B`tvGX92{A#%?$j~k;`&Vf3*EiCm$sV{caeaY?Nhxlsw9Uo z8YBuT_dh&ID+S2yLr+e)CNOpu2bWAl~NvX}4PQY(PLjba;e~p=Bc};(f-0HjIvTG1(bPpZ zU>J*HpK~XUD&)!dhf^RM|Agxxu~|j__S%IGC%s71S-1V;?Dqt4Wlob!VIkXV4g!*# z5}uaR3`E()$5$kCt;m#yDACFC1s&L*JTiYa^)T6haOWkWIi+L5TY^EZEjg^Jtz)(> zwVx>p8(R=`(-^s>M%>r`Q2DxiuaxFQB%+-i1|{KLj9MX(1R`cMMOsfu4tM6NWuw6O zhC{}0t+-SoHlpSYKD8=ZF#f)HocS%wf8(0wHKgvlSYWY_F#FhCQPF$J>RDnOJ9YuB*}1d=;{Qmc01Lold9 z;e3PmJ7l$4nWdr1mKQ4K`-OHJoBV{U;8VE;e~RKW#h1MlSXC&kDSOD4i(J#)n||q& zVg_j*`T|Aoz z4bXO%EJ>_p3CoD(>Ty?=v5nRrPUS!0E{p6?=Zek%Q#pRjTdQHKbLrVzJSR-N zRBGh7%#z|*7xUJcU5(C4g~uZ)QS1Tx^x0)adXYm}sj&SZkw+aet-0}K z&yn$PhYP8vtIr{7Z3U=oEgFHlQq&5J#ZX4$zk0c4^y&Z{?QwMoJgSDjYxc@j6RZ#5 zb)FZK!I!M&Pr5|I8{u7fK@o2P(#_^6KlIZ7phd80R$z|ijf^E$kEP#9jxz|*{_S9P zQ9yhBrkWDo1hMY2K-gTwDDiE>SeV*4xicApp>4lRVaX7-4U6@dKZRLD9&YD$JaZ_%0)v2 zO06eE9{wv_=SSM4sabz(%$y27F*wpJ0^<5=gi$Z|WmX2T$KV;Eg)n}t3~M9C((&HlS}p)JOb`k54xiW{=YyI zYP0Qzi1|)M4>``1<|eXC@3O4rPHy9F+dDF1ri(8!bAJ6D9urJF&I-onOPo#0@ThPk z$ek_3xZaL7Uh=bE$91iW;JU3LL;M9;l7i96y1Kp!gG${lakDV2l@sF%CE3^7-BRU`GxD{P7WyMc| z#bY+(X{zuCUb}R^)UF*X64q1#2>6Bg5$Gh#$AeNf$T;oD<~f-hw>djfxEQHDA-*`i z_Uw{W9z%UI0C;fki;kS>&>dye`!#H|_>wPy51Tv4zNC3ELe zcvi&dkPZFyPY9^~&)S<&wTe8=zm?D%S)e~?K*_dPXO#tc%!6`YJY0MqSA_=j z;q2)sG>O=&nqMOVgz2fKcUBKXWDITaNo{Bex>SI38?2f&dzOoxK9^0FBICt@Q!+~l z4LxYrUzNR&X=E&ICl(F-DI?m&Hy#3LYDJs}ug%+2&`Y6;*TM0Zau3B-@9~Sv9@-e` zmt^LDiZ8d$MAjIyf&RT~);r!E+J&1AC3YpAW&NFfZ&ccodOo4|EH~rk!`IofB7;i7 z5S>Otc(y+^IsExr4Q6A@(v?|ASzWk-OXD$4qJhI{uP;Z z+4T~bgjVweZ<|*B#d~4rGo0^y=@$fo$`&fPU{4*(l#dtn`*R5~cvWzW`?wZ+g$T>1!iD0(MEI70rc!&i(^Qxv&qLdh{q^Dzd`eWuEYkD%?v%d^EQe}k zUPs>v{vH>kb%A}j9$w0Oc;HxB=)}WzOob*nMS1uw@gs$ zA$^X#^@w5hHGQ_xRr&KVs-h7Pya>2u#;~5bC~GvI)eQyI3@ zb=cxn90+Oob-$bysCnrSyCoI?f`vR?7R_=a6<$>&catO+8Dj5rNrttKy-ASG{AX(Z z3-P^=Q9?BMmfU&i2Q2`-j$cT`+h!bMxJGIMA#hCG=$awX)6w-?UL4N~E;Eo$o_}0A zVebI~#+x0vr9N^>I`j(k606H9DKN9CVK>TG_wNlivn1yS(IVq-jwHY3dckSnYEr1q zj+aQmw-g=yxJwzHt3mjWFHS0$0vHLbOPp&8$S%jfMlf*=$0~OuU^2o0*+AK@Klc-w zW>HfdP(2dg#F%`j$~2s5`U@Fc4ruEURu#^$bm=b;P{V}Vl91KperlW~DB`Z}<}FQz zZ!f?sp(*$gUyWk_RpvGqRh*pkbK>4hOyKK~Da=GwoU+g%8{Rq5rS3F@`hY@*lReFT zgQ@9^lSpVM^+&}RGbp!?wnCb!U^md zD8qFdCy|eoSMn|XLa+YQ&IPYO2E}{@_qfxP%hWwR`D$ zrVrtU4LbOqa?V=K)yT3)xo)?x#Cu|2Gt8Lr7Pem)V5l);=Hl=m56BI`ZM-%Q$gIr> z4z^kveYh2+g^=&a+X4xwwL(kcd7F9CkHk)1OdB6DyoA;hin&qZNh^eJMx>d=yFU~@CV6ak5yXJ$+vU3dQPAg&3#g*8WCzG28|4=F8 zHl61*G!%W6=N9`cAYFl%A^T)oESu%DbcYC=EoD-hZ6+L5S(y8Mf69IJVXQuTwWxJD z_?`8TARy125d5uePxnA!MD2NRbRql^n6-&?Re%SR~Vb*VJ?n~-4 zURp(nh8%q46ZP)npO8z>eA7Z$$;kTV88CTw_0Q2~lbyuK9n_*B|FI`zU#xzMxZ=}h zemOsz5N41btWAjF%bdWnaK{XC{{Xr!?6!Qh>mE`_joe_nGxeObF7MC&nZt4&c5Dwc z@(HY4n}`m>eqn0cNxJQQN?C<$SOg~Rnu+v_!CCP73#cMy$wp2& z(eFk!KOO8BeG37ZBPJd~8C3wUDOLV(VBXX8RTPm8J2?XqZ!)D_d^gZ+F}%*|2xE=YY9X z2`*DsCW3?j`~<#r7yK-<>zMhx__i@>k~0BSN1iMzH%O{01qjP+L+!Xv^=p{C{%orJ z2-4(BOy)(stG2(K?7mR^?7Vzstgm_zkE1YC%@sNMJ_g%>e?M z)Ei-9!hP&24iHA;_Q=p`r$xg61Lvipw@MU}h90m|L1ND2Q=x$<22tXz)& z!I-%@Hocz@J8tUo8Ee=7)NYxQohOV?Cy1o4W(ddcMge<`ZaI0H;@;<)J@$Q@n)uXr z_Tmyj@Ms7MB#(hkldB1gF79sCDMwpfT2H%hg-g{-ZdUbA?%YCMTh4{>_8aD-{DOz~cC ziJP~@8aOMt@t$@ADoU8F|E`b|w^8u!p#Ywj>I*F_obtV9{8^}?YBnnRhHNOio$39$ zD`z2ENy1=OsVzW;XyVTA%(F_&155QQMWLwnLSp!vI%HO749~ZpDJY8J$PHBR`G^E@ zgfGo6(*R{$_%UaqH(rF zvrO>BxJ1XaF^E5^yGDlwqi+*f&Av7#qiv%qfDF7G2 z->Gts8dtcHmXnCoSuKV|W#GqGjZZ*dvYdFdIriJ+eYj}54sxFf=QP35rQjW25MjF+ zIccXf?N(|rDO+AjYo}S&X-Xd)zD7KO8-fT~+V+tbyp&%Z{`P|O^1Fv>M_V$bG1pvHPd0&3BY+} z@JU>YTcxX3qU{nAYf29*?-lj3SCE5Xv+@Rh)59vOY{o-b z!DA4@>5e$E*-685J8$yYn#q?7lrY)7X(S_oR!+au3B#w<)>9{lj`6MBhMH-zkWDf> z^OdzETFW*GzY8Q!1}yRD8jPMO*LE7y=ifC}v+D1rYaC9;AE_WXuwbemw+Vz5e64w^ zJTFxyTlzxYLp(~wy)7-vmQ=ak@i5(3iq&C%F2v=3yBUivxO9q>{=R4YkvX@r-mSJB zHf_F)aU&Quct=a+{v&!|E{>gwqY5&%KEp~NYeUkvI)I-<+3#>>y;Uq5?%)!+v0XX) zWZDm>zO3x0b>L#{)IL&AAQ-2^+mE{=0tYI=$9E&tb+V`LTt|q{a3B5R%jmDC0k)1C ztpsuTkD5=~7LdhfE|huEww4UK$n;@W+AkKt8!mqJUKG>tE`GoR@>NgCsM;6 z^Th!<^7be0aQ0yNuB|<|Q$DwR0r z`Sg*aF`thqW202kw8@_Pz%R+faxLFp^Dj4XB~({!s&r9Jk5j`tXhj@stGs*;J`FVu zc($g9<^I zBl5nSRh!!*+j>q=S<0KQswx8+Iy;Edekxg`C~Xuv6y{H_dq7i-$;)M;%^CUYT+o`} zUyFWprCqHBtrwstLdV@3m!xBYA+a$p6t()_b70G*8Fd5HPQ?i5n7NLm;o(X4M}_ay zNOQ#7R_N@~7LiHgBhE0VB>0N44SY}Ijyh)!3<7%RvdG^>W4KrT-Km+97qy~H(c=o2Q7 zux}8>@2`;X8XV`Jeq>@{X70jd05n9#E=f%4*M4XG)#^;#&1Si1c~5V;XS<@`W_CT$c2qCf zv%TM4OnQoK0g>Igyi&u6Kb+~acYdV)FB-!z(%)ad{!#l|2kqirL0ob%+!ooo_Auqw zgqkDJ31-4g4q;m+6{0H7Vi7n=(f30oZw;Rm?>&8F3lubmitdDR17zo`@t-W+$tsty zq-ve2IdsK1G7$)#LY~OTnR_C~88ArJZ8j8uFpOj0#Z*hYz>SYDW-KCs1z-m@0okS6 z%MP;OMb)qm%oI`y(PHUP=E$s#*;at6`^*6>%qJ?ye4Vt$Vg6cDL}@*dWrx2u$x8Pz zuQ-_7M;(i!cR6ii-%}?}yt86x(=Bd)xUHV9($wo#kq^e&Bs@8vxyGq+dQ@kp^Bg>X z_UZWC`UHXOQv>JL_*d#@I^6rZj}p7^Z6}qto15HkBWBE8i~(u6K- ztHjm3wn(|OM}%FRWQahYG6?DX%yVEIzCLzGW75Pex3^!7)a$-Ts9|BN>T`6YAJ85u z4czjCWJKtvC~sBwoH_?4EbL}RKPQ_XqIZfe60wYw3OvbpYu6-NxU2}3HSETStKIv6 zD@e<2ICU2fTQW@}@Xw~C+2T~DZ4&f1U6QX##aH2n_j6RH<0T`*bt~lw+vLcJe75Lr zt$zx|t8eTE`G5B4JPmG;TsbV#sG^kZjDTY2=*H)YZLphCBnjl?<=uV$MaZmcKnG!z zP@|`<7Mz1WIFE173-lIU|LYLqh?(}O=i9IGDR)k=q&4LpwH`oNsH}Tif8ieeIguf* zE;3e%^@-cBc%dvxrul*&=_2`zY8UJ^<<<(Z<*zBrjL8>hCqYAjQN>O63|ZzZ4)d=) z93vAr`MJ`tW>*^;3p2(5K@^l(OfWYlsS2y9C9P(RohL}iRcq;m_6NHc;RPByXfw(p z_9;4gz0087>J;+8*U<^VHLnMTK(cuFFds1Q6~d`4O_Swglqhh^`Mxee!`Jrvt)I+# zWp>3bsd|EK>LFW%;}+|)G}G0Y-8MIwp;M2LP>+HI8vTo{v!-1a^W$JE`?=`aDi`I{ zADJtG^yq=NUq|?EJimUg`+od;Rlg|fY_`Z$6+~=(@bV>=Ue2`n7jcz==r(#D3W$bO z3{$!T)$Xqzl)2P*p{Kk*KZzU8aQwp2cHBCRnw7K4DDy6qZgN2KWr`PV*{w*>> z>MAW%sX#xDbgh*Z+q-OnzHv}aaxy-uo{du12>q)?txlL5rHJI5y#%bgfP(`Gc{%Lmp|&)$AJL^jFp>2EJb_5 zl+4V6=?1UTNP5O;$_N_bf~BQF=aASmW!{*~`X&Uzq{rFV4`9w7R=?^zrmes0LH6NP z=Uui)z%#kTm@tzsTk435QXD^kRPD|~96v#3;YdD4W%tkvS+%YKYwDF&EtH-E4mJkEA2X59K_5FxUrf$V8 zPR6l7gUk?kTH}vELm949S!nPNQ#|IlRrhIE3S@1;m>}c|921P$uf$Q9I5e{<29)`J zYiG4|^XkCB?2a(U&c-~jI#B)bm3L#ssol7M`QDnb)ka}iCt#T*iAF9Ft{xL2aWvCPdI@)MxY#BM;qI0O~5Q9mr6 zEQUHc$eBffCdpEFIP0j#Y^F~ci^0uf*^cN)2E{bC)96EqHs2|O%+RS@saya1%UR%e zwe(jd%Ah?WyufiO8Z9yj>YzK^K)Mx3jH2k;ImFau$Bl3;tO-B0+`swyN$yR zch=G6QhhPRSB<-g91Od?YN|U#ZSt?@FHX z!!**^9gHmWjJk7A`g>JB4gP>SA7{F@*XOla>BHhv!5>z81vp~16KgRPuc>0{TC+4b zQ5*V&hGk(VgR7X6IHV<4!tBqr9*K=lXPOk)FtJ}EK}>ktC3V_-?`J5C>Z5e0n;~&+ z_v`Yv_4ZF%{s3fzLOpQey?RBine$@1Em>^?pDN-^I`Wc+l##*C+^9clo1jHFMO;yt zcU{`c-j&YJ;~FN^-y3MPk`-y-6%yUUYR)uLaKoO(KKF7=dq$I2EDo33m2c0~y$ux} zB)Vx$jr2Q_^-g4{7?nT}E;vM!R3Ocb4Nu7m6|d&zyrZLdMs_a)&l8| ze9JoJ#w+d`RShs%4aj4SGRQA_DkOuOO)qYJz<=GirUKxq+NZX{_9B6b_ZDe#_iyKRjW_!h$l4xtU8|ywtFS?1$eRbQYeu3ckhnT%H zt}T(@H6kwjOJtI%D_UW8JQBbg8!_t9uKs@5vp>K5m^hsB8FH!eAzC-AxmqSq5?IkC zI6yX+adl|m^mx8^px7HVTUECAeX>aLF{-hYBV_qj438dk= zaMi-jZ6p!e9$#ono%F3}wohh|H72P@yRg0YrlSR;9_y1+IbzX`t)8gZ-KG}A%{@le zpWo}OoQXPIq_MYs`B9|8-*hF=90Bg(2qBm9K;ejLhOMzg;A}^A^vfH~Wt^FHbr&jI zn3ZPo!$p(}t``Lc%s5>$<3<4a3d3S#DsWM&4{@E2b4buO@^1lW9S@RS@xqS5n2&&D zQ<_eMxyk(P01)Kp6C*!^MD9)xE4>AysSER%m0QV%N z^lR4P&g4U*s+h53lUhUDkSIP^BuN&OmynTsdntABoZgPydRmxynjU^vN-CzUgsQz- zzw%{8Ww$MTp}dih@4-d32$ei}rHcyKOB}-MdWt5-2nKdv+`UVBgGk}rwM!1!Xs7@| z={D1%P3o$lYeU34<2HmGHtH*>#}vYrJI^@34m)u|8H;{Rfq>(kK$U@BTo+Covk=yb zB9YDq0?`Mw;rKLy@Q5|8Lu*XA0~HSZbcLd)U5k1D$KK9g{9o7!(a<2d4>UjP?tE0r ztdJoBb(#vasb%#kiar1Y-j~>W+z+7OePOTl@Q?xxzIU0wTn!n&TKx|Pt*~ZS1Bb>m zdcaOw6TTRADR>)>O`@FPAy7Qs7I$=HagkZ`$o1JyoT^{36w#%P=ppnfbAPz z3}mFIG78;7jpQx!t$yMHNGT#`1R1>H;;vOOpy5E$P&}IO)E1yM~Oz1FESRa zQ)OAAvz4HMDoaVajvS!`znt;`ApvLR?fJj5suK5_@N_`VvI_e-XJ%oH1glcs#X-ht zGNPr6e3nUli?`zvI4l+9fsSSWR^?D~@^xcr&W~#5_h0DyiP%4eTgOc{x0=uRs?Zb% zecyiPe7T2ih~IJXgrsjgm!W&L*VsJ7*)PxH$@@CDbhetpe5%zXwK7OIUU4~fpy{cD zmY4z~=TEzdRm+(!dC}SM4oF+JTqnjHU*UBh6Ob!HP+YbP60>ikQV3pUd-)pqia#-^FLHwyPw=AG4u}+Vw!sD2B&-( z0yNY&3)Vbimn7B;)&^#T*8_jFK|vuJUyk2F$t<1NRD&8<-$ssSHMXo;8-~`HxnteU zUn`eNZEA>7tDLt}%?C#(8i}5$H00eM|C{XqpocfLheC@#^62f!9!ZK^E<7)wuNE>F;WX29C>iCd~ zgas(rL)~?E-Zr^B+>P~R@Sz$q zW>>L1Hun?mgsjoXXgr}i+r7ZW<|aY1;W2Ymb>r(H+iy_v0PDIb#}8M1cc(q0;0e0*O`ZqlZV?=5YC7(XOiXSL2)1JgsRv zCr=&X-KQ5i&)@B4p73}b0%&yb3ou()^D|p_=(C9A~E@eMYXKI;ZMw6g_XemZ(+wPc%?|Wy2 zqD85EWJS>5y#JYAR&~Oe+C$ih9oB<-dQr(1<0kvG{Q){rp#VM$=n5Mq{YNvLl6Ms7 z`o|!z;8cmN#6exCWb{ab00D~K9@|2wmZLZ_SSh>rDJB;_G`Wxp6zr3>| zg?^ptBsnmx#VujVIwl~h`22$=E$_B>u=+(u_assPP>H>A4DXcIh*`kwNvAe`g*Vlg ztefm!bM@5E+Q_BL)+s~sRUL2Y$*Y>VIgj%47b)DDhkBo@gM)9%gyprBUC>u} zI)={mt_2);a_f_wT|#$wF&O}P{Cmm@0n{G7CQm#wE7;sa4s8rHj;i?m&J~p-v*wKv z)2`~-i#z(n=yTQ%6h6D&`I;=1EtJm!4lCuYPRln9o*;b5?6!e)u1+AddEN<^ow;P8 zRj)P*`vL^<3lyV6rZw_vc|93)H<KG$&BXIq?yJI7^O=}g?Z`88ATZUKKY>B}v zll3~Nl^?EeIe-n;jjV=N*(_hBfqW)rwH?FU3C|=Nc;z@&?q*lAB63EJbODsL#rRfw zJG#VTn`*dLOsShY=eP90ns@rsFZQU>Thnx#iDyx8f+Zl<#Io9BkYn>qsY$$D=y`T# z9_t~u3Abr@FUOk7*AYdOKDJfoH2K#o6>$DX)D4KP=wWh=Na=6_)K@%$CIf`d|EmHC z;Fbu&6>Bk{&=zORY}cxH6Q21W_Dd}!Vg<)I0xQo^>B`;?V`Br=I(JmQFB!;jGEKvH z*`Tz3xgO#ZU{LJ1psq|q&Dc22x9Wo(f#;V^Kbz{GO(ILyQEsD91R1iX)I+tn@f0kI!1`-^-*91im81V>lZIlcFNORqG1{zJ@uQ2QdIPLTd>VH=* za+F8hTZ=LUhFmkSRVR->+O04W2DlqN8_6UDTp}$qvTvvUMRMjK|5m_1xwWEL=`2JBrZR_2Lh~!XD9Y zQN1($eVz%r01^h5Ea65&$7GC4mSkX`l&uS{KKOkfVwSE7UJH7xMrB;)*9@~8xG6Rj z@hk;hHJf;2NVrV3pcrSY)@-G$r5%ArgolM16cd>yu~>lx)QzlrhnHo~SEU&Iu|2M{ zabM)d_nOn`hk~0UOsDG`bN01jiMi(UXpS5vP|m%rWHgq$4l8bLoZpJd%r+C=VMWsY zqysj0M3>TblBp**V9}ZlC+rYU{aMMdF=Q+aXPV~9@3Rf3i8ha;%JO^|nees7Ew#GO z^oohQY!hsi6mAy*;)mAIRA4{^m|R^!-5^InD3E@{Pjd ziQ6slLm7u5DYC}eHU<8by%ak=hF!r@%S7oIR7ILwn`0rv-lH&tWrhtrnxm5;Y0gxs znw#ts$q$AcXooV-O_Gx4$Z%#U`vecwaPZw$Je^SVNrQ=|Z-pgV^H{7~7NhXRtj2r1 z=Eh%$jS)6*O$$<5?v^iND6$r0T91gm&DS<#d|S`EM-ZryPC)kKgs>_~hNG z{&gKvTZVAVmghzM-hQUO#AP|w#1&Nq*)9-sO70sXs-feAc_GLTcXOr;`*pI0NYDdx3Ab z5S25de}7*R2*Z$9=noU**D9jK%JNf{g3{C8{ZN$sIeZhIntdz(`5Mfpix0`>+Hj>Z zw`Elgu6*qioaSKwu|?wFJn~zY=oi92CQNjIX2@Uw(YQ);lgD$m=MgaRU z2L@d-v%0Zwp4>#eYlt7GdDx=ZP%IRl0_bsv=dNgkS`P{?Sg|PU#h&=v+i*Cq`3=C(CM>rpb!z&<*o*q+YSH>!h%<#rTCm1&9Z9<|QWPp&*oy6fxeN*!C!%8g&%qu*YeoA*u!P1IVJW+W5T%iyq`!i<~xc@9G zAfaOqT)#~y9WR=_enh_C_0*^k%6$@gW!Qv{t3!IxdA)4P85cl8Uv`GMXTidwlTowf zu)@w@Mhly6EF(Um)_#@yw643u7+E!h-Z0RwBSIq;z6AA!`~c`=d5ZWJOKhJ z*=7{Qfg`e_!`Gv;S03(5gsk&p6^2jXj%IbDIlBw}mn(0{h57!~!87!*A=Mo<7_lpW zgm4{$HbDnF-q0&wtn@Opo&1y04;E+BHXlY&g)4G?+&q8ipPnS-C^jHT%(KRb`taZQ4g0nj%|qp|jQM)~Y4R z(NBp`HMXeW98Ur5F}&b&!p2oaMxat+dFe-=`GT~LXn*%*GX+Kxg^D11F$tv)D$^&$W#sxAVXSntRmDtg@TR242`l z9?958*q_1cB#9%T{vm|(3$)@1Vm$pOV38IgKEd6nD}Qp<%F3oSHMu;knw7@j4O$}= z1ng2ve0LLTyR8h~YO8!QS)CG@N#JJwf37MPFCeV%3mX0ae0I<=0j1>s(t(- zfeY-iHU-nWidjX0Gj3&`H9&0x#1t&yvCo|n%v`r1qRwlMWxZ)>Us`TDt`Y?FoRn2JdHU=8p(~E2;TE9?oPTzL4050g%~A@ren^2SbpLXll$mOApX5`%&!CxJo#vMj0|W(vL0QW%F9LBPW_O z`W*ucXBnx4HW_gR3E#>*ytEH!pBf)6n!pl_3Pgqza@;zL59B=@fe2I$B6}#?)NWn! z$L7t|y+5iqkvB&->@ml)x(W@AiisvwsJO-M)$T0VOerLbIn4qz%9_6tiKg~16R|w`o#>|gPr;7v#$Lai97^*FG2&NgPF?HP zGxT{a36NohSQD@kCXt1ki%!BsiwWs${>sGLssc1ko0e9=6NV)AehsW9@r~3EuWY=I z!{`Bc^2uk|t`0u4bAryWNQdaNrnMRS;>+*HxBP>D9Pj<6?^u*) z%W~2DU)~SLvMqX>@wShhyhPk&kQ%-PT!w?*T>s&fPD zwkQ*Od2>N=5@JuDbzO+eC(1DTzAbpwo)Os}P6qLg%1IucMRCtflQv(f<2bOJs`ey? z!{~#HB0-L^rW%fp{qgRkIfQEqfOV@^JJ;l*0FEIb4ejSJ0FMAI_Z|!~wcA)e+_46C z5U{=Xn?8sS{m4IwZ~2}d!FyhQAD&0~{XYzR^|yZwzwxQRgkSy1{|sMy?JMNS>!$1& zuZundcB%Atj{MTtZ!6|P066!(Z)mrYUVC` zl*75`B33d$P4iB|WeUvi|Mu}t<@b}F&@}3ZY0tx`n>9|gw+&9>@QjFk_aFMic^95B znPzUF;oDF#(;41sDWrJ?*hWcAa+Mj)iQfqa(NM!`|(RIWw-$~RY>AR`FFcHEsC zV5?|Bn}L7xANa%g$p8D#-IIp*Z{P3!*U!U$>&x)3zJX@wQ3X<;w4N=}`IGcCuRG2a z;pxAUu&2Jf^d8tN@5c)t_?GSbl|=mPfA}xr_507;qUGfT6w!80_0zowI{^Bmt&$}2 zQ+S)mK?lG+L6+OB67eakcde+XO0S=5-icK^I3gpYX7sTDb+wGBO9M6mSZ{r$&%}ic z#|2kV9=ZjLxqms;m+ROY(fWXcc7mli_Q$#BG>P}u@o%d~GU;B#q=T;Ty^rW>R>?8( zy^}n=^KyR={@h6(o<-?{D?b&9I&ftKu?@Bx1<~jJZhLw0&=bux&OZ2tEu$4w$_RDq zae-(zuMq=w@D^uGoGNEw(2bAxec*fW{r}XzwH<%$v%iiv|NBqFzx>)%F7Ic^w!AQL zb6O_{%|0pGB06sz8-ZZ6-)xny>SXVG8Q<`K`hok;Z`cUL-}#UJMSS&(zcyvk{&4i{ zecdg`bVt-KM6J1R&eun2dyDcExpmM0Z0nX-0l6>82^+cwl`7MTsBEW3bR>X^ZHcUT z4;=6bV7bn&SRP9_cL#%z2?6Dw5~&kV8&;~RdP0-}nBAB6wQfg|AHD8#!~xWHi*`IR zlB*{W?V?5vFj>#V8cVixuAc`bQTiB^cf%>$GP6Fq zH6e6Al#(ja;7T0Qcm+AGoIZLs${+u2x)!#&SIKF_IQZ!Q=U>^5-u#=N!5e?!Z%@Qw z&&tGM=;=-$um4ZE-Z-4Re{lHTM-?{xsT7Y72Uy|P*hWqc~fKeM5#h;HcZeqdMD zvu4SQZj(a|ot&}`lQQ_O_G-Xvy{`gK@@c0$rkv#A853R?94EGqa8ESx%1_7B4k{k1 zG~;Vi+i+;)^5y)KizyK4P|1Kcl$J#l?qyJ7r)ID=ksA6g3>f3-gfUwwurz$&V}A7>*y|wc_pOWqS0zy!*uMED zUi;_@)ni9KY|ozJCB6wI|)@zTwXp)Fp%k=%bmOmhyrk1%#FsKT!=O0T6P- z7dF)zq9K}95N#cIAlZ&H5OIJiOIGQ0#nL}w-AqR~kh*}Wq%8yf? z5ji(ZK4X&We`b?&v}Tb{e$_%dGj$UmTl&BlxD z_UbWL-6&lFe-pQY@tpyg-vTQ4Asl|Bp>T$|OPzZ^+*?GRq1az&g>0~D zQ|FFWwu~C19)Z*{i<~H#(e@j9C)0q6<4l**OaQPorIZge zk{*7*X~_k=2WD#Eaeuj-`H80}=VJ3RK#UG! z3f}z1U%}m#P`I`ccZ4^7{+BlL@cX{&<9Ok|zF+(5SBOMW>K-jHoki!p8vkCGw1ljo z+W?9{5Gg`EUKpF{DyHSt2W?d4qS!Z10A_~wS zXdQau;gK9BLytFDqwzDfSDr(qF`M%D7e7Ut1$evPdTdci^u#lk#KDUYjr!@+Ak+>_ zklT3N6U{#6n&&RZ>Rvz&>*|dAo#Fe4D(`&H!?PhLd3Y9uM=B4n{ltDg{ACQpwW5P# zJ~!MbIg;(HSpvSUo$gt#GuFGin1vWtOB!r-%$^{`fM<@aYKs{lBSHKql%@S(DBALT z=tusEjW~o{e(S5im)`_lzsqu#5>6`WU{rSGRBtyQTVr=q-gUW7#rZef56^FW&$c1* zrH{Re7hnBOy!Jo-4DNpY^T^Ga0>s#sEv_WvJ%8{!@f|<#|H3c-*uM{(>;oXwV~PiW z@7qoQE|k6=ezZauGW@QQkpegcT5CF6UBVhG0QI}^Ymg&Bq=|gjZa`;dSbHdY_u^l{B7@~~H%Djt|D{;`uE5 zhyHjGhZlbG^SJ!XZ{L%KuYo)?K*S(tKM53*SwaZv?UapJ1(=NM^_V54XPb@8p<5#} z=l|Wy@Q=I#yZ`)#|MMsCyZ`M^+%LO)K}Z-YdgFij)r~xS@CW`he(i7l7kKkN`oPM) z0hmHv^|Ohvj>^eXEu*oFIEma}>Lk~EcV2ruwCQwz2FRN2{&*pF3vpf2 za94WOEX(8QqsD!llS22Hc%B>n!1c)*>k6#f9&OS(&nfSSoEs*eMX{Rusgt9(=u1+mg#1dDD5` zM@;O;eiiunFKm}z{*mtoUVP7FbCmzJ`^d!g?}hiiZzB+LkxXFO&DVvbxc9ZCt=cRr zWUO$nRPIT^%#1GgWMU@@_3NJT=c6qRN?dB|W3$kj9+WPPf|=O3w{?--(0K`WIPx2n zBTosq%e{_Iqkat5KWvoErlu1CsTL_tt4$NceucU=(TmlnJ}xl(Xw;L(g|*K(4qsGf zdp{lq=EqL%4AX>R%N%<}pw?1Dt(H(ud1vG#56_sGS#tyP(2bCF0yfX!a!gyxS+l=e zWpkOP-cZWXkT4;6zU?u9xiN_`8^@THBtQbWd5^J4ZEGlLeO9x}hyM6Kx$C=+IM}b& z9)W6-nkgw@WA?Z~1iW1jT{e5VaG*BY!3k`zC}UafsFPiW!@ZvPxqI^Po3Fv{iNlNk z;D?YMV3GH$Kl|D3^Mil*Pj4im8I6F=EMJc697Wr~?Y{kUslpS)Bkjc?L}NR=RKpAq zn7ZS*ZgQ%qbU|D7uROLnuL+SbI_IrVU@XyFgWz2*qkcsIXXE=52t`c1fo1D6z+}sD zO5~X1_lktI6{VwEOa#Z9=H133H_>;y6`fL#_FSPza~;uV^a+l-ht;n9bXR?H9Zq>R z{}l!Hhpt3;HcoC(l3PfY{eG`qNIXCp8iEeF6u4>ANz zt0SbRR^Vk#Jl1@z>AUO-!k>TBhyL*PD5ib+O}y|weNlk9lcOx#YP}nMe+NiE4xBkE zs?c|kr(*KP6A=+x<}yXy|GY;ZUi`?1?n%Uphqip}epw@;4}aiC|Cz&dfX?QxAStu) z0hkw4WRr=opLJmPx%}-sDYka+(eg-rTU&)N;CD-b`5f=@U{z6fXWOD4(QN$FphkL9 z{oHEqs!vdzI&${=OjVs&<91p`FVa~(EpIa6iOB-UpGX^Qk%xLa=}91-4LLVVzC&W< ze8S=Ev+iA-X0bI?X@j8#5QeUPBE1aCN!Pj|hgl{3$(!7|jd{{@WQWtvphGFmkL`T? ztn_BTI_hu?nN|qGVx7}whb@<1_p(#SQO&pK_CdpoQ9oN^KjqbWYdF~{5>weG(Dzn0 zD$Ty}Fl@QkbB#Xy(eK!nE?(c3C&b;BbN}71;&=Y)PvRT@*!87RAH(9G>F!fAngcqkJ&F!+aM@PmjxyuC8vn+P3lOkPy z0t+r$wz%z~E21CU=WpIeAAb9F*f+g|7ryP4ZHXe&;%?m20lmNdW&F-x`$@d?y|3cM zKk%Jf6yo}K%G)Eq^SNKZZ~gpV$FG0tFW|K={7T*XO|y3zNPee1eZR~ zG)5iDsGRCQgK#eMhYj_;#%X7DbliS&-1uMHCnS=;k;rC6<(JAj_y_MeTH<{OpYo2$ zNgkdNA&xKL64xNJB+G%6Lgam9Xj<8+#n5%EMq{o!-!Syf`RR)&L)ZLio>2ALk~%rf zbs=V$e=w7yF4x05pGl7RJ(}Gy=o5(Z%!qkVPggU~A_%-$G9KN`dH;gXWD!vfgGL~T zsO+C%JJ)}F|6jiheABn$rSJPNe)q5Zgc@y-w!QnE*YMg;{48Go^k?y&@BDVW=R3Y_ zTlP5GGa>Snj(XxD3^W`mt z6Ixz-#$9wn>56@pzbMb!$ru|1_#h!|B6ThJ3$Jh9NK-#_Fly3y9c|4uXSnK(4lE_u{k9Y|l5LrIZTU;+S(pja;GSAQ%`RP^%ao}jQ4Wd%y2X*24`SU&}Ga_viRYAeM9vM9?~ugP6!90oWwAS@*a8Oh#l4%)ISX;%(F-}YBL!xv2JLaW zBnI&mML&)+vqV*MA;MW@Eu*+->UiN3fXv8Lc5UA-WLp40fHhjx83?UFe$|qBG4c%s zxjB)U`p5b!ypdar@5Uh>(%?< z8(;oBzVu6<#^-|>r`AWDx6NufmAt+dx@HjTu(0H$i1nM`N77;POs5K?`M?2P7&a&1 zSURm*DY4wl7H^H*(_LuxK4~rImTg#V2^@GX`eYmnY`XKZIUM@*R0SFc&`&CJN#XF) z7hi~LB#4n-s!~Q40)1_+K4SLv3t!mxKHL+BZ}`zq;PSqgvwfFh^&XArPr0O%SmaWc zKK>Wys;-)bbT?9z*Q=4bX$Z2q_s0O%QD|$c-YoSdMz7=quoc&`?p=u#Od%EP^lNrbm(tl^Z$mXkcZ15%xz zMsChFy5oyHPDu;c*94rs6c6bL`Ejx_?^;ASx{HqRaryCb!h{?{?Qqn9&i1@W1pp#N zflRt#%g9cxq9q8q*g&==5O;EP?89{)*KD_a86=GI1k(aL)RZ|h>|7BL=Zs+s z-z(?Du@UsAejRx2O@UCa zuf2&c{=(nhA`sVo5a0Pn{tR{^#2rpMU-WO?9BeXK2-K8EXML`(_W#yjK>Lxrl@DDC zt@~_JMvQf}l>a50S#k`Rj(^ezA|<>*GWsGvB^K=yzGWtRr#yz7dmbJ^+*bh3k0k;e zX(&Hvi88v*XQUeh$_*qI16kH=+tnm3uHnMDe9E&8-n%i7FX%5x8h#eTzfK@pB@t982{tLX5k?YajoA}Z%e;VKX;eU90P5Q6=&Ht3JlLu6lWmt`E%l-$<<4?HoC^n258BoWrCE+Xt*^3A}L)yn$SSo2BL%4IRor!LghK8rNrSi zZ5+oVo;$W(*7Y^C=i@;RERfX1M)|OEe#p|x8D3zRU1r#^`clM%l;)>#@*n~i+E8e! zuZPGr%uIw~WN|*ugubxbC;Q4(4iZK6@Hg*i)X)AV?6Y44KK#wV`(FZn-%B{<_b!+B zzPx?DfcN92Z~nIJ__g2qRs8O+{#|_SKC+OH&SLI+uYctW8}WGGee~hw`z4ImzW8ff zirk0{fS2ypTJ-BJNUSBB&_cT<#757ui&5-}n5blVu}08{!v^f}c<~g?;0OVVROMw) z0uXO64SzxvqVcQb#nV>RCwX`Ti7;c0L4&NN@=O7ZKwv$y2wl_r9##qv zW$MK~;1UF#hbYUKF^nm=zGDIDX!!Z_R*@{JX8SHPhpfVAj2^|<&yRGb=*;2NAVu*~ zq1-Jm_Mq2p@r?-_f%~wxg4-r-v88QWO!A&_*_|x02@*B&n!S!z6Q6y5aCg6aVL$Vm zs+&noBUDL#S{iu*VFdzOwZy z3oqO=hkIK5;!Akpeo5$sm%jlof9rQ_Pgi{Dv!BA7uYCoJCe7jZ`j>ta?|tQi+w0O_ z`{IASowb}%S2Y{u#Y8I&`f7#7HS$Gr-XjWtuf{L5o@GOgTYXuTO$35BYw}0w>1Aqq zvC}!8D6NT_WboNpt6aA;`tVqC?s<4X=^fb{qh9s#%Ys3#!j-z5kSfbb#$03CP3@Cy z`cjvDMv$|7F^2+%H281IaO~m^@b9ooUJVyu!72%f!)^y+5oXhljWX`+RRq>p}1PH zo@$rTzq@^-g5J{m%04eZO5$L9AMJJpY1vxH8&ooZwMtm!LT1N$TYQTg+`aw=-u#`f z;H$s!oA(iiU%g+-_$pp_>1Dj~(LaJ0-}g%9?XB4F%QZ3=bpVx5twzbK0b|^_)JYq+ z?}NzkWEUBm4jwy4vRlT0Ez1{7cn-&XKfi{1Ytg{fwWYOV445-{d{Vtmc?dbl!vjjq z3Wo18+Uwx!K7Wff7g5+pv2|WhN-)m?d`ifUhZRjLB*UTY{?}B^Qw-cbg9z8)R31ki zkVj=-?!g$dv9PITru#XQ{16Zg2U_(ezsR6#%{NkA`Du(o9VcK4LWcSN5QxROaDKYP zVO2-^G-{X0UJ!_zwqW$VP~u!E?gM0FwmHZqlsVq+l;~Qz{UR1Fz&F>W3hH*1HiB6C ziHF);r}tm&F>b47d)OCxBIn&bnRx9tejRUoX@5NSn_m5gaCzzFtXtSBL^Uo+U8si| zrl*N!PTt22`mLMt=Mxept%Mt%MaC3vq^mkmzhOPM${-&U!v)a$qoUuOw1q+0YtgSK7+0|BIxuV zp!@!dM5!fbBFbqT6ta;=WJ zJ+(h_bR<*S`kqV7>S9Tdg4~7qZ+z*C_e&PPd%vvl9{j!!eRQ&A0zIq#Hx&wQP?LIF z7@B>`$_^rdCViHc49(hot_L`~ef?DE+VW*2$g_U){;QS3h0@I8(EUNyF8h)gacNOA zn^Zeac^o;(!vl*C=g^i7#o6-`!u9y>?k@As8!lb)JK?8{W9tLFK$i1s8oxE<6)O`A z!X4>QuKiWD7oqBWL3c1c@=$55)!ootp^aZpbh6T*PM39eF69T;$hiCn@*&G7BnePy zZ6_L9ldO08T%&sL8KNFZ1fJj@S+7xfo3`$CBxBw)u#z#{Fh0WL>QnT}_ zqZ(g?r^>w}*sw-QBAR3qU_#q{b#GfaOcJ4*Qr`ID7q-0%*ZmLIzlPvh*F7um#4&0o z2KoANldX^4mA(6u7$c8&Dlqpqe%gn}!}dMKq>Dw`2$^;T&2``6yJyGkoswZ5dYt1D zT&b3CmK9AYeZXNj_ml^blRP}2@UK_e7$`v?ybYLeeA7#ZoY_9V?GE+nAV3GovuYXX z#;KehLS6zB>1KzdFHzD#YsTevM<#GR6%`7?k=Ig9z>rDe$q}L)=?a`uza0RjwLjO# zBV$s?ZQ5-KeYMO2Da5Q^tz&%iytV>H9gb$^6XA*Tqg?sfak88FLTxed8T1L1dKt*3 z@zmqMlC;38o_qfns1q{cYOboVvMs=;+9>u!xMV)+Y1}p@L_`Zsu%|!2`x?GBHcY!( z&#>aHl~cJle8!*<;*g@angm$C9JzLI2ZY2h0h--WqXz@wCJ-h2)1goGs9obL~dMLGuSdHqE|Nh4=3pTH*JYVMUe*Q#z5c9jY%}>*9x(I0?-@LNO2Qh zHhmH=k+ua7r5xQw8lFgUPI1_tpB;d0OaKo3k<@hmizwm#SuL>r>s^11 ze_sq7K4;X)lk@@-W$=?UJi46Z;Q@u$VE5m-kMsMdhV_mIYF$hB*&-Q++1>7rWlkp+ z&I7jt6~-)Y4E6-koNO?n*jf+P;95BEXt}wn8O2Bk>Z*4;X9a4dz1rn+VW79X?5wX5 zDB%ZINX3Zn5tm;5s{y*ZHz!Ll3q;9jOr6e%aTRZgc(fKi8hJSou9fo zj|C=FX@@5jfR1+vLyZlm)jfSGg2!z5mWr@Jb@%Zt47JL98sRqPdt2qzi{Xy)XSr1s z`Of_+bdH0}YW&@7CTmYwpHu$+1^sKyABNRLXsS%%PHR7OYJAf2m zUme?BPMT{&7z#N3V7?em!b6{BR^FK99YLpQVq5=@SxY$R*?0lf+C5KDAfTlH$oEQg z0u}uj&7I3MretE~YrXDTh`xo=flgk!eqYpAJgTQ&wi&GJV3w|AW^o;*N6v8@kuCY6 zC$c@#Ov^iQ&g#A=aZQzmAYMM|b=UZvvCTB@$ohuhEPqz!w@Q-XGazcfERm7^s4bXY~m zvV!lcFBrOWSVsC>Yqua=L0)e9&r{w`ImyEV3qKvvNHSJ^y}$KQ*Cl<@rJk&2JRV-3 zJu(gGSclFX*ASn(m+3rn5e4aW*&*=GZS~i-{~D!3ou(`K=wcU5+nsjqW9a8N(`Ke- zRv45Q+ZH@)8hNt}FIHP6`@bXjW_v%z)cdwhA4|`VKknSM^ z7e8rK8(#TlIH^!OAZ9%yv8`+w#}|Ww1UOv_-5S6f{}Ka1 z=b-1rpBV$c@5G{CBcK8>Id~98Fa0E$OKc1|Yc&Y?Bcn3Fw2p1F-hMZD_FiB{o9{MV z)*S3yw_b%T->V#(5|(2J?>7iwC$dr3Kz*Ug+4Xlw7&zm+gUU*5-kdh1*5QbSAWBn} zwHdvkzg}iV9Fe{deIqwQLf`ApO0m1fWe z>Zh)Q6H4s({K~fY==(RoHq+;x!P6EW-}g3HxVxLM9%uO31w~mZ~}ixZnTMz)_9$LW79u5Qnck6&KK%Pp({8`xEu_bTCUGD zp~|6IG;yZcH7-Dv9ili?DZctaUlNI=E~E^q!%;Kh_zn=@S=vwyPdCu?eG6SA=w&96 zf~%uYJd!H z=kv`_vLOM)At7{QfDC_QZF!}QcYUKK1AI9bN<4WuYc_T=QIQ3gIz$r+>58;8)5&yp z;CQWOLs?dLz}_q+6y|7!%=8V#V@6-v+GM=xJ04qfw#^EFb(W3Qf1cx|il*blj)R?cKPUKjYEKG7l{|9UGGFz-zL2I#m)Nz#5W96)B;LSS6YsB0r3fhw9!tFo{tGl`KgurNUZwKJMQO$VLh`qgK( zhZ&D0y~a8THtV|4&)ny|MG+V&lX6A^$xqxqp7JSEzXBThHt<)yRhDj{ zsV+V3XeBwO(EBajDRBW@r(@lQc0ZS8obniQl7|PDa;i)g3i!5f!}D{*VFrEvRYJ-e zP6PcMmg|RmdHRJ(0>5ZQp3vX{y&Bu;08PrMcQLeI1noZ=p4YNb#L^xPHx-jVV&l)1gkZ6 z1~hMFi_VC=Z0WeqCMyzxz-kaz8)L~^_gxMEM+UFC`eJs#!4M)@JRbHoGH(I>Uyn(mu&$S zJi9S#&J*niGm(BjC7&589vj_JAp+*q0?pEf^#D@^G*<6okI{HoPCik&t zLTFay4gFZ#Gnc4I2MH=IA4b&EoyOB7H3)7p z4-YJb8&-qQ)j?>;^VR^k@wnzZVG!{JI<1yWI8rgcplU~fm=#T+iUv*~vncA|JadYf zSsV)D_%tgsYxc7zT!`*;C%>CFCWuf|U~7DE$4%F%gd^%)h6Z^OzgJC*2}}+0Hbb^$ zViS{J<`XV}$n`KOyH`B&xD>h7=n{{iTHU8^g8>{&vThdXV|#$v29AhDKbr^?jW^BP zK)>CiXg^!E7m*bQ*5ivPBc{kh80&TatJI0S1&K+Zw1dtK6JjiEaC~*rxw?nNJt*?F zb&eXZyDp=?JKMw2)3R>2P-WE0aWrHh5Ima`NmX*-?R_8+4NLS$L}EXeBa0Z~BQg zTg_$+i}KQ@?!5YSC_slo@C=7ZkbdU6zhI6LF+!^&eOduI_*1K{Vnv$tQAuWx!A8gi z)VY;^Wsr$-xXjQiPb<);ukvIYLBMb|TP|>#IZG5>a=&QXGS(%`1j453Z1GF@EW4x4o zrpm>ny_6jDqdA-0Wz>(+Dr2zwMY|6%; zXrGN&>FkyOG(!4F;?#fyvxDb)nJsE&agAP{!??#xs)cr)FZa5U72e7Ty%EvMK1nw0 z#1g?U1!$*tSxoI4Pl%L$>Hx(U0&lHsHLibxc;v;@Zgp?^`2cQAZNmM*tlfV^zg_pl zi7=w`1%mm}S9od9v3l}}BVMc0eNm=MyVh78_r+-ZAAoTAtnBeEMcrfWyH$?Ucc3rf zP2*1T?*H@Il59*X-hM6vG2_k6Vxv(w?0tmRMvGaJ| z-@$&jz^t;Z9q;Nc7ZKb)Lz$CqtzVvB)ETXuD!-g_0Gq84HZol_KN4_>$nY$4l;LSN z0VUf-GwM;bv2?SaiAijdh(;W^c1|FqFHN(ly?F^Kh>1<4q)5)Kr7|?#0pivMsx87% z-d?|+gU%&o*29vBU~W%ag!sp#;Wlwt^g`Eu$d9M4bC9(DSY>GcvZkj-7wV}ivnTtt zQ{FZ?$-{$7fi*{8y?OAt)5qX(9Fl2YeVH9XKg}B>XHqi;VniRhNfbT}2fY0%9=h}L zwRtGlpk>>PMGj08u9o0nk{y+S9n3H-Gpgt1uQ&7bV_n4^!O?xKqPIx@j+hdQtcg>4T)1$CAYE6V}r^2vaUU2R0un*SriSXdpvG^ zFdakkJ|U&5f?CtDoWa-qA^oUxyi9AY8v2$OwhuUF7jqPZ$0Xkl9}3UN!{f1HHRjb^0+cI%3o8IcX@Fpy}={>_SeHW(ZH(WF)h|{RDwrgsxrvBi1Jdjkj7MlLepxxS?o9JRh!u+6I5P4A$ z8P%8iM?1<>7smDj(`MIk52q*}GuZw)zHIkIjOx{h&5|HTkPUwS^)AhG$`|)(`%2@* zQ0JMtPh)DZ1@=9jm+FOZG1k!ge6NZZiKwE+o%5r<-eWOdrY*|qPj;k_i7{?HP6Pdc ztWzFEPV(^JVlp$1U(*zP#|l(^J+goW0_n-CBhpLG%YvxKs<^zku1{HP@0j<}b0pz} zFFdue1xTj@E6|>GMN|Yi11A+slS=#1l#0Pj(rQG4e6)a2oOkk89V~ZHm;u&l<}{Ho ztZnTR;fqz>qAd*V$%FyG2s^RAov;9Tu2;OSxMtvx`<7EwIX~W7nO&31taKBG%5~~% z@!1Gyl{u$n;*cbd%w-+f?wIp1%Mt4GD$jzK=1Y{xM>u(qt=j`eU0rVp9{(R3xIFAlAvLuXCl zjfr$@uw(kGjO?e&M*7dz80HJRVXNvy`=fxFn^pa{>Nbd@ww{LYVohAC4s45YWvrSC zW<7G8pMA6EPD&u4m-lMir>%>a+O6fff?>Dc6_TnfFVOBcq+wB(sfy({u&cf2HgqZr zt9yqw6s)&N8Ki{R)rJ%I;)4p)iHWB%`?(~9CNi_A{v07 z%o-z#>PqT@Jbph^OpUls#0C5`p*8A^j{Fl|MC;^WT zZ`rn%I(%-^rU{RZDH%=U$R?XIy#YrhHM6F4tyN$+tw*A9 zg-JJm0=gLs1@=&f&6XU=;UjRH#Dlz5ogv?ciu?WHP@4LfQJn zWgaFoLz%4_C(={CDzlZ7ZB+fs`Zdsfz|0Ay=myMjv;?CduvSlXDA6Rll7K*>U!3;v z{uy*!jSBD*TdHxa90H}NMloyWk7V0>5j14~v#D>z@wAwrq~V~LTa%yiJ^DU|%tyYC zh9GNib*-bND+oP{Xv8TGD<^q)6tOA%H?#K{MAP|#1_e2jqd{DuMbM7s8mhcW()+Xn z>Qqdj8Rf}!U`PH4Th?43kb-FTx?1lvzBb_$dp`2J0G@tUR_Y7^Ck-(z4?G|IQXSR(PJTfe(lsAtr$Brx<{_HA$%Fv4PM znAp&dtF3Ry9M)L9g|SC?#Kb6&uIrfE+hv;R0+77~3!=)oB3kxDP+E?svZa8u>N>hj z8mD_4_P^RR7)E;a!R%t%=^brraO@xATM_kgpM4P)dlbq{%tWs0e%<)JdQSwCE$NO^ zdl^MPk9uci&)XIb@#(_4r@Revl7~l;=;qqL6KHetF~Iv!$NjZS<<+%Kl~qu|pu}RC zjx&VK-HKY=l=G7VcY;xCxNf#6JId=X>hS?e<^6b*UBHrI;jkD;eX7aijB;sZ(JoGU zjl5{4PZmKyPWg;LPi9}%ZG!?CuPO7zE#pvXRYri)lB}ein9H+KViJeiry`r{I?y`W z&Ba1I^ZM!<qG23V_EH3W7Q|{%06*;Bm z-AR;qyqx6Wk;H~`VIqOUnVijtekhIw(l-5`mpdK-}q&p%CpeDR03lT5~@LG(s1x)u0(993_#&(6 zb*h5Sra^6B1sA&u&fqi=g37a#3kJVlu2#-y*6h?KavfBMBWd?-ULrB-MfGcwW((zp z#qq2UEw3+ZVu*3NPtv2?ZRH?HEqyswZ!5K|vunMlexIi?4N30bR*?l;wuxaRhE>lE(_0 zzrsB3H~k*3_y*3%!{fEA0fV4Y{qz&5{hUqaL79pvsQc1rWGO`7$q8GSM(V zzUXGmB0c;&6jLTFUZiE~PbNBXZ1nC3y*E3p>*OojYcXW7YKx(F6`h;jh0rxdKkekg z2oQ6udVOX>mut6Nfa#4mv@5aN`3JJ9W{!TZGP&$~SdHUUu+Wh2umM%eFoAx2=kXuI zH;DpTTc1&fN0s;Bln0mn71g?li-CJykF7*}rAG>C_^ep_%JUK#Fk9#&Vlf=94BePr zxH`c6aQ@CnR%W&0T3woMKxT!!>C6O`3E(F%7>;oeqCR7CENmjDJ}*}o7!YloOVEMd zk?zYGfZ4fT-wAs4li%~BKZh5;;SI5+i|W}wzVi9^;j=&f>h}E)e&VzE#&7wZs$Je3 z%0R&M*}wHFUjOP#0OUX$zxc=xeL8{UbAR{S@ws38piD2SA38q%qn}DWU-{yF*+2a% zKK3U*H8-do+aAYXzR&-epZPH2-3KCz{8O9Q0Hq*IiPqGI&19ov_WkJ7n7S$$p_rsC z#|-Aa6$R}@geA!&ib$tH`-O-ONWvVKT&@E7BvilglbJ|5Nc}JXIguRodl=ylxXql> zNq=%_J&+d|`90eNY{O)}=R|8FC*z!WweXX6J8E*u+bbt|cwkvRwloH%Wk}QMoDLYj zp{Me3YVvp>fFkb}dD4kHA2$aAhpwC2ABANYfNzOQ`YefY2bt^X_D`09(?b^qGm zXTbDZBJ2JU`K=irv+;QZcuNxK>YMa$7WKcf$iCg-xnBZ)>D3Z(hmP6l+q)dax~^^PO( zpu6ts4rh}-)F4p~ExQyNAzoc_6bqS2K!7OtJL~RmtTVQi0=ncwc*%fC)il8wUu-@J z@=)mS%vjYV&Upuu*|UNlUo0Rp;GkVAisCp3KL345-k#r6UdJc@^B=++uf4c^f8Q%# z!AJkt-vL!;lNaCn20s2LiX?pIr(VTp?xPNGhY0^{?Y`X;XQ1yDErMELGL8)T`x}iQ zbmAaOPT7watdi}f%ihhBUdA0<^<4YYb?*$PJ8sk$EM@bmn`>DXsw<}s=T&%S}W_^^YD0bMjjqf=9&KD zx$s~a_n%{;{Uog@$Rj9Epeh>Rcjd4&Vxii4&a!L116QE0bp-v=kOA1Lkh``u*F;mR81Z0~D;qW?0N!l@!{`3Fp z|3k{Zmi>+2@@4$r|K&fQS^mR+;r}P#n)kpkdHzrSCw~;5{mEA~J@zG%Zts`B@QwKN zCqIgh{U3L7@X;Uod3^S#KD;eUMB9AykA8Y17FT)wo@jjPuYD}b`uG2}KP$j@l@I>! zKZk$p&;I31^Y8uZe>UqYX}N7o?W+5*0%jzc$OxCwFSCXM19#6N3YbOuqRn=!GEU=^ z%N{l*sYFDqDydss!Ne8g9_iUVzhsTpM^6M4hI># zo5x!c+6-l-)4>DTTjAyHj@MbkIl1@;NixzcT%f4oeGNIYBark})&?kiEpzwKdNu&Yb`dgvhW&Ba8Y_IdfWmyQYgRU7Y_KzyV#afpIkZM(9f0_miLgv5#y^ z7FT)c<=64Y{`4oaocDe6S7f>3r~m55xBUuLNh&q_S!)m`i~%^-xX+?%@iK#6;E(E~ z**1Xebr9lUSVWlG?}KE9K`za(B!`)lKs97BR&2#Sd^*gF*6*+M;n8=9oWzzwz7G<~ zi!yv+3$-El$$gmwpxl){PmTJ!4#@I-6lGkwE~y~(T3gT?jzlJ@Ukt!g9z;&^@JO=4 z+N0V3CW{MiWn6f8&k;`C&Y@maD`4CU=mWoBCr4C{zdIC&f4~Aq^7N`+KG#QP7mT0> zI?C}?nydl6@1n>6>X#_DG&2Fo<(C8!6C2~2+vHxw=SrIzzm|AhKWrGR!~5uk>2;U; zT>zp_mP8Z^x&HBV?-RT(TGp~&5tZ?kb92Bj1$O60Y}I>ktrKz z-P3St;9s}qt$|6t^}wQhU1V}!FF0lTjJ!450;%VmN?s6S!q6Vz)bB}z=Yb^_Gbu@b zP$Sl;-6}tsoq4NGd%G4{!C`af(^N|$MfQmm-j3?ZNMPna7i>L8LW?B|Z9c^4o{4lnEoHcr)LzuzhJJq3KOJ3{g6Ns`O41nA0 zjrTg_b1B;f%D?yN-=FzE@h5*0-}RAS-Zn~x{FVRsPvEFQijd8HE?dH^?hj}zVAyDt zgrfuX?u%7>)MLWg4osf-dzmZk7`T`O1O$A;Py^ba^hTJPI0J4Qr{jB!l!zq6*vS6t z$NOaB@#@maOr%Csk=@bC%o0fWsU{(`&YyHX(i#p1JJCR!`)J(@HKg`%v>^fNS+V5e z53!5rlv{F=hX%ejb19|M|(xdEF%W`Om$w zYq4&WQ<;YONd?chaPqQ(W3E?1jv0}QWSZ6YEC4TlAWZdVyLf9aJ|$h-r2Ol@cTxj1 zAgA8%YrTC8Q!(KF7t0KpeE0!fZuCh zw;Sfw$z$>^r^%>u$>MS4+%WmT(g8X~nG%ZYCo}vpGhFDK$wo&|dW>qMb#ktNO-@0| z1scRN&v#T@zdV0AKA%X)Ob_BH9s#wfq+Zz~T>&C7i!tyfFn-b@Q(gEOe6M=8HjGVO zbuBi7k#UH2+>eS#knLI}ih6X?H1X zD07Obp5)!xc2M8!^Xh=tX?f&XITRV+v>ZWBWEP`;S$8lIb_ng1 zxGp;ckhmIy|F8kV$?q%xDEhP4$F&a2&4pBaf&I8A#zlLAVRw(Ahr{u;v~yHLOtViA zL9x_R*F3)490|dqUD)~Jpb203&M)AmP}}r#Kle92g75x^KZ933SUNuaH$R$BUvQm7 zhgE;;$Rs`0<}$a#%lv);$6VeHRT;V=4Q9V|G#Qzos&g#gPw>xwUj4kMCMw0)ARfI#XQ(s18=6 zMnW8_;@M>Mx$vE+&3FX_0(lZ@pUg z;!;MXebf)*RUlV=Kl=auYm>eY{DIHmqyLAW+n==99JkshdlI(K892CI(vDVV@7G4V zO%WTk@imbJOzqItk~!X!%{-oAJJF>by4k<6aou0N1v+&El6iZaxFip~$3gdErPl($dL z4U-Qjt2e9de+OO-XkqyJch-$H>t|rs95}L(%MOg59`|Cv;e6r9Dq~o&D1*H9NZ-TL ziDq7P+5N@^{BkHo&B*8Ah3u`EO`Xqg7^In9?J5`M0hX9iFC$4%kXPa`$Py(m)P43$ zV({)z?clC)6S0mKWf9vA=#NG+vEeeW=dbT`xW3mR5HeCdLp&)@QI7dg>PAY8n1cS4FgVj+vOw=4=f6f zgCQGe?NL3BZe%LgBC|X1V}+arfLp}7zldlateBS%s9=jv4po4iZDex-)q_F3@HesP zjfy0FQ9hxbu`)ZrfAQo3oEY{w&}PlMK1bEX&1Ueq9$t{|WiV@VNb_z?Ue8E``+^8m zn$_)`pBTY%##J*%A`sBWQsq|L{xYcV{iB~w9Y6h7KZc+9Z$7c_b-0(8-upU!@c;4? zs3%TFJy4N>sV{e(v1 zvb-fovKY~b>!15=o^;CFE+=_-K#2iU1fJ#?hGgP3E(*|jux(eGlTkSyHXJ=DAUR0O zWX5!6(bwVmQaM2WwYxgXBHK)cvTk|rwlaL_%)(>bb`gCxRLA+rZI@(YI3ZvGNQPtEVVmtv-!27(a+}4rMl#T@Q5!Kwn#dsP36*$PKy92Q-Btj7jXLzFE3U7i z{vZB)y*%o}AO9>qc)x_fk46T8*pIz_CcD`#f2}?fIkUnT6Gni!?5=mOwi2^$4q@_^ zb8om7VG$JR*rMB-$)On?YJqSx)tumag_Cqa#{LGND4n&Z#Mhi#}lGQ=QWr*UOhAB)eP! z1}0#;9$#Ms^(ws#>QnSs=CA!PAHe7C|3ZHF|MtmylJPopYpi}AI`hVSxNYr#$T|I2 zk7X`&vRJbyzgQy($a?|?rywmu!a1GV*z@NJC3du@hpI+^2nxGEU9xhdroArS@K4#4^JMmFq?r-Q0Xg8ofd3sE;I!0h5U?E)I?1_8fYl+Pe2&wwt6f; znIi`V=xeFd;X0M!Nir5URL!)5`)*?ko>P?%(*FMM{pIb4tfYsKjo0H}`t*0<3!i)C zemUX|`R$5d{<-g>U(DRg{PDm11Ng`PslTwDxh`*f^pAWRKmFG~2I-3nVmeidJIv^# z)qc`rrp{GML!(x$yUOt4GD&LFB9+}hi)Z0uWR+gmDuFGM`g=7z)UK+fSLYeWIX0rc zF-{n?o$E3$6Y)MIO+$N$avW7&9Zns{L6aYCpQ)mxjeSyluZpnEvz}=w*Xk{wLUjCG zx_C@E$-@H+4}1md7*vbyE}p?4M*46tI+GoGeQ&FHXJwSfzeSOtM9riiMJ9)>8RemNMh}6S0{KA`b<&Wv z51Y1GAEOT0F}@4<`T8F5@`#WDo1OP}eed7f{%*;2Y2x$0_R9W?nY2mr$N$m~aH*T- z-}<$0+1~YVB@I{k-XHuqeCAWH;w!)Xeh6`BbrIVq66V^ijY^d9;uKx!jDk}^Y>0lr z=Z$J5z)Sa}mS7Amcr>0`;nq3m8aIekj0sQX#|j&rNtTESa96}3&hH2HLbfD2PHlaF zxY?(Iu=sSfNZ5f6P8&zYr>4B@+-Aix(b%fy=aR)cBEcWi6dpTl+xrId#p z%#xI_Porf~J8B10HKyj3&PY33*S{GXgKCH0WA{Jh_aOG&f9MlW>V2pDo`u)&s#_n; zD7PFki)dhMI39!or$%`$IkV<6ZrPj3z20xY26%T8;YjG1?YJ(y@GMU95Rpio8CEEJ zeU%sdg!*p#w|(C~h7bR#fA&5W|7FI4?$@iJvt+MkvKIWhYfkKIHlR zAQc3XK?al*3?05czx4Z!5N$1V=}&@SU9VnlRZ)M-q7$E1A1vw;q^;-6z_)es;LAGI z(nYh=pd4~hKcm-FM>)QV2*Gj_rrgJSxm;er```OPeC^HG@awykp`NYvzV!fL-R^!@kz6XLqEd0o1<7c z3^S<1kr__S+`)fPKsvfTObn_zG;1vE#KaPmhrbt&6*B&!v z-DtBq(vNJbaV)v2KKcV$n838^;Zmjp(=tqy8U0PjM88k1EC!V#c3L;|YkVWEDbv~> z_q8K&YRjpmBfqw45UyD2+|PNjN3yME+$Nu)t(Jraxp0csc(rSPHJRL(HUQ=g*L=^t z^;@Srpot8P7u;$9v)C)u&mkp^?F=eGyNbJvcp^P6MA8R zJ~^m&r*om-4S4GJFQg~7O0atu1mR#X0LbByin;>*Mh?(A+8B;!Ejg14Hbz&%17DAa z=}wlEbM$h|nZ;)gWX-XoTHFa-<3hEqi+@}M+UNFYO~BmXxSc)kbwxn*#Zz1em%{;{ ztS6DY0Q5OxK=zHk7I`OU&+S8pN#aDI%fHd}`dDdI(snle9tC<&j{r1G%!1nnq-I6F z_G^YElMtrUEOwu|v}M}ru}RSzN##ww^;CjQLd(iAEv+eRowQ|I z#FHYH_fbHZ^e5-@Qw&q(go9c3SEeb^fs|}Z|!j>+384EF+Y|Cq0W}1`H|36({u8yOawM1UDe^6)70U zM!K!rli#BbUZH2Kbw)mpluYTEm>6;ojqan_{|SKVQL?&M$fd5{2PesJ2aN@;VO3-3eu4HOE7538P zqxM{oVWD7Tnjg8`({jM4o=Te=JNqerw`wED^0uAwKys3Y2NWg)-oukvMIs^}e`~W~ zg8)Y8(jleSy07m~UGPBV*|8Jp*-wRQF!WG!ehYA!aR9AW!)FcRVC~0)0>W9(=I8bV zmvKBf3)e$q&%`=8y1+?Lz}tyI9=7ycQL`wS94><^kr`g@;r6Tg8fbDSx;mi&S`!<6 zHz>F^J<>*|Mrci+4Kkat6D)Y{JNj6!e1^>b3Jl^^QlNi*! z9|uFH<)>sqkq&Jy{p{c!U0vIN+DS5V+8NM1uhWolUju%8k1-YffoVbmWyf5XLs;td zZ2-pMd9F^HB>!^0LHuX-U*O-OfGgrKFP2yq9@UmT3}nv|z=;AbUrfa*G6H9%-4^Lv z758?!=!rJd;y&YP+bNGFCwX{ascB?# zFd+bq8NRFS30}z$`qcW@#cbm-CU{>^;;|nS2AMrm8P>snB0w&rciLbaVRvV8MtN8n z>apb@(0f;)y$Qm$4bzS!qsq}ZhSnm_uZDLJq8k6&2df_AU#ERZqR+4-!xIUbmnG6G zTy;c>+{9c};^EQ6N}YdYrB@_zq`vL=1M@W+$hGfyfCt@GKux7iym@hv^4| zVEV8xq69os^o21T{^w7Q40x+k#)t^WtrJT5q}P($+*gGdU8)?nxxH9wCLLO;E=Hdd z$K@GmpV}v8>EI!%H?@tlUFP2t^e7SESH72&2~QHU-WaWqW=R#PODuLB)7?7#tTB`g z%I8pvMQG*3cW$rS^_jmH!HV-`TJ-t5JE2zV8ujH1Zj4hNMNabYz~WwmlO0rn!-T9f z4rU+~9hIY%+B|t5xBx{&CX4g@ov&X=6ZNTsKpUM6#P|=Ik@eM|qNI|&fEA&#Y_E_h zIWH-LYr&Om=h_n*IT7xFF-+|+%^W1b9;UPSVebx{M)qTh40zKiKt#ZoWP&Sf@x3Vp z>1_bWJqpWXG=j40uW~(swCg22n!(g;>VwRda<_iqrqSCANf=rJb2&s!#b6$)^L zUDP&KrpSpX!&uMU-1Vh{{%DBmW+r9?WTO+#@kYoX)w#rnc`UcWuli+f=UTGA%bg5^Sy7fd0tD z37WKmbc}XK4K!;svCpV3Tn?uZveIsvMo%0QSwi&#wvR!5x-X%7Xh|EWF^nKMqa0R5 zvA5HmGvQKGM3$i!%lgDqJ)FdkbU9E@Lk+9PpzHW3B(I&@htE9z9oqggx!CJ7?|F#N z+3FahK)+SBbTUVbz}%8PeNrs@vN&>TbRSQ7G&#w`BM8s(b@0?&=XSTaQwCOrXR`+n6aHhvG57kZ6XHTeibo zkx}*9s82%znbEWTQFFsI%d-ivhVCuBjq@(vRAy*VepM*X^ZPioRJPa9AQohXk_~hR zz0z4gYuqk!$AtUMAWomw>K(_q-^2(2;M98?`#B;GM?(jgUKAyJH`0Ea+6G;(mA;I0 z;Gj%2^n?>}pd|;Wej^R&HWQ8V8sc=lvUsSFagM5M5~y1KGAdW<)dvXIvikff!KTQ zYvAJIE3dP36$W?%EDbLk7nR^ZMO3O$4i~o+2h%9{UC@^9$=M@Oi8jASAb?;yzUZ}3 z+kqyRV=R>L^;Txgj-XFJOnh&g^-z+{RY2l|zLwx#Kkk*>qqQMNIo3p{t}*5q+mm+D zF_6)o$=5H>4}#F`%YQ4f1=A~C->REr`!Z;vflb=&Sx)HR$I3#vR=L`)vb_$N4oVDiYa$vV19EiP!imn*I%>oVIj*_X|0tt76$&xD;!2pzWS z$f*IE@#y<04=yKpcwpHbLxE%8s2CDp;eKOIlVFs&c&Z^YMHd_B@yyEw8A22T61N5j zQ7P=u924P4$K7ThVGAcTs&9}TcBo3Gw4K5w#SE%kl((|9UVFi=b~O;-CoihrMF{fV zm2cEChQrI*ogo`^!XyUq^4rbj78vftY}lU+MOt>h=e90`^$Fm&srDhc00WeZUfI5~ zXur}&sByh;A(CX)>^T5ovR#0Q`fiah(z~6mTq|rcU`@`Ex2MWk)^pKDU4o%F>Ez38 zIZpdFAWJOclKKQsJzQUoF6b?f<*vgUKa$zOQfk@R?Y7qMBF{t&EKSM!mmfeQ{8s18 zWL!;S?34$WlRP}I%xt>eEUDvSUQ$?#Pa(pgl?G5JCWh5xjpUX}d(%BiBxPZHIRnGW zA-jHT#%Vc0m<&1tDIoK**G9^8n2x8NZ8-lC88E6-2aaqN`i?1$mhToj)o0tb>eH`U zx+!4k?{9EnKL3)X5E9*tSyR+eFDUqhnrL6HOSTWqU^zN7HynvIKy39fFtAqp1Ssws zPJ`r`fORNvZiYD5eho8-XqIPEVF;lZr~AF0?5DBFeM!&J23#y8d0)MGGyxqv{%IUL zk|Z0O#|g#>SF`h*4;>I-j%=eWUyOCKZ~vJP}?#wE@aXlQq3Q9+3)XItb2cHeY+b z3*6;tfR=@wPs}jL|L6Bx!#`(Jw%2dv#OFG)8>RBH9(BT}ykQ#-lo#+UFL2Z$d4Mw~ zSZ?y%E)lSgU;3EKF%YThAPor*JMjuY>z{34-2R?qA!mVv2|^I~6Gb-C2ID$`zRDt9$5OxuFbZ#|yt2wM{W+_7POj5_nNR^R}`@nl8S#ig@) zmfVbP=e?C*(V9*xSWS?NyCiWuo^J_n26qah)$WyuEpjH*Z8j{TE#tye4vJ`jNKcTq zr<_cj^7hC{9v)EA83Hu(M@Avy`t@^vNz?l6k1F2EsNu^0=8Vn`yll)!tUOdaI>%VI zHsNlJf0KWiAh50J+8h#b$T~=B3`gc`9ix7{(M{2cPwjb0`VJtx2viZ|1 z_Qj9Fu1JB@<=|e#aoD5xG?2=)N(-C2`0Ciw(sk<9bqj(p_8IvA)J8xwi$3XK&f`11 z%4C7uPwppiG{#9pB=r3rR-1xt)*m`+=@sP5hCXRr$Fh;@R$$4B$JG`2-!J(X%T>4V zbtipzJUPk314_-fSKi6#k2*g)86pxn1B5LmPPEw!?A)=ZDR}g2=o$@RuOj(IR24W|D*c$a_QqT#C z5D>9jXHxNb2a52RzPWNWPwSw^Pi-*pdNV0zk@HKTTEfWR8;B|z?K(xcu78($#5D3v zv*l)|1QJ^PB27fF_O94lK}~RL_t)mn>E20NTb+b0#=3QsjPdMsHfHTHgRf-}=^5=_ zX$|KlGA)G8$FLkFk^Q~xs0>~60B`!{Wl=6gh+&qn<|Za%#?l8)ksLsxm(j;-ug58m zAt!lwU~yNbuEGw}&^J@cKr}wT*5l%6Tx?hkD&2tPiNR4`rVG*nw3$zo-8S2KIXG`# zZFVD}!qna&cNyhSJxHz+NZfP+^cxeg>CiL*9Z!>Pn*poRlq=sHr0m{z@Ezdg0~Q9v zIM$r?nHxkcnu_YN@Rgyf%KG2hdz)U}mhC*~8S7qO8~fV11_A~oA}|6$3!$`V@)P(K zG-yY3^8@J9kK7;)2t7m^v;wq@SNT`c%B+nbG~ckFhNY9pKqMxz6a}l3Yw0!v}Swz?pf5GdG@;kNaQug zaqBgq`g0v-S!Ox3zwigHs()^9Uh|)cdMmGnimQUjP9VhNMDBN$*{NfkH*&#bEBf?? zCe)u0DXm}8+3UR8aYK!)=V>67pdQoi1qd2JV?2@|l2$~d;yoS@-S>dC>zjBgk9;XP zzk;Vd)3@8I8=@iInXw2Z?$Rzt<; z+40Ru|GZaLtzNBkDc3p~t~FeJfIYCAf>#n70l6}g_1=AeLK#^0=14w~oRz*EWOe=D z1)O?0I3DZIpM};qv?qpUc2_+`d1fQ#QUFEwB{_rY&iA?R6$z~}g6gDwjH^BCWlfS~ z>W#~C07-xZ&|5!8%hM!(#|AUo9*#E7d-{3dYjenjVJ;l8?f%nRn($A7kBKasO^0$IEDp zsg-Y^W0@tzj0$ALT*J4|X^U~OL4m|Q{;~_K=gzr{TRssM(jWxj4g$EX_%d@tl#y~D zZEJZEgUeJeO-LY%(^BFXy#aaZwHtNJG zT&bF4QmTRWN(6cF_E)9H@}$badcO8{gWmVenU=LxOO>W{n8bngO|L0l8(&90ryTO| z*`*IrA<;Jm)RuGId`aNFA#)yEsnL5*-s<-eP$d0>~lFG!U7BCp!RV9oFxSX+|4SNPuWFVKSLusaa1Lw@TMpQJ|%_9%B~8UKLo7 zk{M8Os!HU1?kga8%Ttr9u#+&b*}kr=2+&;_k|d$3ypz`ioEJ{OGv8gENV{UAeZ(Yr4prbS;#VmfGcig((==f97PfTUTTD9l`jmEDANewJ$irtBlfkab zcSiSY8+-4KAQlfE?gnua3h}^l9wDiYGhumUypy%9Q%q}cTHQwYc2_~dzQe-K{GD%E%2e`aa&x{ zSKy4M)(&0b*?CT0>l#B0X?j@!b`VFK7{OWJhW?p_Vk! zG$gZ<<+z|+=qfC1qX$(bqItgXO^L9PbaD66W6yd!6Wg6hU0IgrReqkZ~U$Q?z zy=QcgVq~r5IFc4>1S9gIXXMFq*8#+1JH~6?%FIWj$gp^2P}O@a4tG{2AUv62{Rc{B47eyeUfSU zoeqeZ+?le%w^kaltCsp_X{a69xNBWwLl_j9v+Uqnt4c_KBrKNu*q3Ry;n5T;wA_a6 zk=^B;d=+^yA{kB%;w?Su)0w_6Z%_o@JCh$yg9*VhM0wnfcrW(-44d`Vyolb2aS#Ko zVUvE88yC6HH-ljF9upQOgjwKOZ$(UIpNj@?DH3>+z&2#r+$fReUZdr{o+cNkHrh#V z0-pmo`uW)~`Q_x0htDiEAfAKFD9A=?b0^)?oit8*S_ai(WO@~TU4R<*~ zlODtp1R+elQfOu9Unx@G{1v(yxZW1pGS0HTs9rhT)Jp&rG>MuvQa4VL(xn6zTH*>Y zxfV=$F?W*G8&PYJplGRgdS%^dM2 z;cJ@WbtFN#a9sJ5>CnH&B4bWf34GNPw zCy;LOwTOa4Ki6$oiH5E9t;59R#!GrZ#=Rf>;79o0_kJFK_D6q!KllfhL-3vt}lds zD8YM=7)Z&_p?%1h@Upi_ZA}%wQu3R6srslN@=^k$jo7~`p`Vrm#1a{{wuZy=-i%a_ z&V`!zYp;+V`B}>$51&!Oz{kD^8V8>kf4`o1KR^QLGT=OdhI6IYU4sb)=fU$=EuAb*UZZqOgKhyTy2d;eFSBo| zcYRz6M7&rl%0GZ8<3t-egOx_z>GQz(f88P-D_zN{Sb@EN5(>=V~~^eu_lk%aBO0G4i?0t8(R zPz^gsdJG!gT%-3{I)I(qdK6H;gpk>?x!S=Vl80pG9-rCOa+C=id%3aIlRm1%WQWwK z*^dbrSL#|GG~(0SmX{6KGgKygQBe@C=6ALdlMc9)KH4n~ABNVGTUI|XC^+}@(C(S7 z244YrKHU+&<`IZyV zTFmO8elzxC`7u)PS*G})m3kb)YZ4!3BT~>Zub$ZNq9#uiaGduE^ zmha=pXOsRmMcoH@86##}eZ%B#SN7d-8=2721uiwd8T!}hBL6(12n)MQ*{$BQ_Q;b5 zJn56OX%AB5s24c_Ubn7<3FN{sCeX8<8gC15$T@fcDxEoyvoi*a*y({@57xkdZKH9y z0{F0ylX%58%Ae$h;)FaP7wiR8-h>X*V@-s)0bMjvV&SuGYu@$Czx;21LmK|AZ#-My$G>+jOx;45%uX~sI;=_AHhpzyQu*F6B;HdBV=IB?4`jW(EM+jt=gJ z`2m4mm|dfE3%K(#K`3;vtOM2&;zGv$r?ydTRaZ!SiZ2=_X7%ze=|PB%%|dTq(=!wy z43LpPL4sD#i5tbswyO@i{)?=9CM%IXHsxRL(X(gX51)RQO`Yg65)mXT;>Dyzh|;F4 zd|x_h)=|4ZkKglQI=}b5U%(H4_}8xU=jO=Y`5*u5Ysq3nEA8t|6q z2k6I0Yc+#i5AtO6SajOncD6~_3)>tYEzcmIvxrVIz~!WKUv;+Dx5ryyfX)ys-?k{? z@|xmDewK2`!)KNn1og&+JJgl8hmEZl;xZhQIJ~^rGU)x2bfNKOJeV^JB_uVDiG)JZT`mLdt4!gtG-uAcgC>- z1Kjm7BDDdIjN-dGB4v%roBa#LGnRc4Q~()0)ILjI*0tU1!pEyDFP41{sPg{+zj$3g zJq>^KyMG_Q|C|3k{^<99i#i#NU3ksNf$ZDWy<9Ytq3M-Ic^)nU&}<8eTzci4?WRVD z^fJjipNad)`2p5yvlZ~kwrM#zy#3vqZ3bYrlYv#X(a&DsO>+?UxhujNit zSoXJv1!SV=S*~HE{>4xJ82`_2|J|Y4zyGULH@*TqClvo$ZOYPFlNT1JA@su4W)C}Q zsI_TOM+l~KhTKJMpjqZ!L;db-k z=DsW|q_-~8?wrMG(lo9){3dbj-!pI!1)oO)R&)=dtKYsM>V+nbE8Q8jgOp5Sfx`2aC0=Z zG5_~({&$!60l)eSuz&YQV}=!9f@(O?4Rp9*YqmaTPd#oRID;(}!suRsayQ&{u1iHg zf9<~iY9-lXX@92@y^w2 zWLHeaO(sH0_TSPm@EC5LW6KBjkZ_&zf+C%))kH5C&)j{>dNy0sgQ5`M()Y{+%CP zf8u$tmYjg8{`Nw!65bCH@d9L=)Y#c?);HX&30T}cCeZp!h`j_$vt}B#k+PtNX~wE! z9*vrML+}!itfgyQK+ca_fAy$sdTXY)*nRd|J>xP#NU{PdpQARoqP#hqHqUa~_`Er% zJKEN&90YtX-w=mi@_*$z{^>vYNBGCzkcUi^8PBd8Ko^gB&YBWN2)n)AU$RF=-zZ4zngn@nc-M~;6@;(9~GDNeU}LK53Pi%s|= zpHB{X`0T=NK}#mS?7Y4n?*~kzGXv$Fmlq-;Y2^Dr<-rz7KQfyzy187zdCfu0xKjHz zylG?TR6;fBq@uzHKs}Eyse!M6lGC@+H&b^PQ2p_5|4;b6|K)FuoW3CsFMs{t0DkoI zl{HYa<~}>MD1l=$6(geJ3+h?L+{}au&;sC$%A2Z99psIU(8+q8a~xaqY-B5I%oTjE zwfzOaUu5|p!!qeXffMVvwtf%|q!;BO;9fm1Lr(CmeFv?7nNiBzb0^_r;7#vcsdvS< zsCyS}>l5$K8!z|;{~>vY9sGm8{a-=ZZB(d3l;nDL9wW6x7nIAEWVkk5 zp#OP%;U$7@9-@J%uVubO`WSPSFd)4FvQZlpK{B-T{*AbOGcF~L`YI<{n6PQy##W9g zQdssytj6NVXO^>J@-s{I^@3C0G?-n!O(80D$+81^@ofY8>Q^(0j281?-gT;X$NMCI z?Bb_+Z3H56L?uVep!s&K%IE_&1kb-0nA92Tqh<(&S5E_I*FRU-fBX%3_|qT%4*uGI z_Fv%Vf8}4t%YXc9z(4zQ_#gin@cioqM3s*>WM+-F;-vSXr9o=p#q{}NbMt(*S0Vu; z0%e$>*7fV4U9XvPdoRLmgH0W?Iydn^acpyMsLQ%kISu`l%bxD4f8Oc!PhnoY*V367 zSw+oueo}>>%l!>c`ThU>?|jp*egpsfn`JQzorQ@3{Wa*wwEByST0G|B@k+Ki?bWLj z5igrS;}Q?2vT@(JG#d7T$Ch`UO4o?PZ;qtG=p?~2JzH!NRhMuPtEC;IS2NA@*E6wH z3J!Vr@^Z+-7nL#ScoStlX@fMSk&3!qpt`j(H|C|193aa&&8OS3C?;X%nL3y)_!4A; zt^|ZNWm~QS7;9$Yq!+(B=@~Hz%=|R(lNkNK|L5PspZxeA;$Qyv{!{$gU;oc95@Ema zO}GD7e-XF!b93zHyXzz0K6$=T@g$+Y_rLwd^*W|3H$1|N#{e3_eY@@dk@L+MpTko} zPRa6S1x*CTu-@sT)+Luu7PWkxsLc4~rLj!kQ-8}m)azi93R4l_#(;U*m*I7S5D50L z?KWW_`TSzP{vZA)r`ErsnC*abK0^)U9!BOgcQabeNy3(Ku&pkVm@Rkq1jlUAO*YHdK+OK0_YBze`D6LvHW4j9% z;&i9yBrP(hs@%T44ouSPpTMFV2vA-t-&-NJ)rEF*GR&Oa0@Mz!(6LD{?^9Z@>C5nJ zuLlRu=nKp{m<@S;De_OhS)%x--w=m?{*ymm0Ul26Lv+@=b}xY?WYC-kZbo(T6%EopbnXY~NI9@wFgyHMSH`V_)kLTel2`>`_ zV6rhDgxwbF%5!`%z%;tlbI@iRUd92EN@O)nzE${6D*UEA42rC;ZW8r`IbDjFdgQ{X zL);uZs$Sjb{el!7_5n72`}KpfJt|*Yu3pbvpl1@)KUcH zw<93wTPskiOKB>=&lJ5Yq0h$aslF-E?2nH=u2#uC4booqR28m!K>|HK_jpsc76b;Z zF0*2!v*dm66-MW=Y>;W0M6O1VO!oxrzVnzqO55c%F3^&R6MMBY9{ECYHcWm+={tp; z+;j1Y7ww66h4$_`hZ7rp<&^(@n zZjTJl8{Sgyh#$a6YStSrXgX3JP5=@2%&RO^&)k?UZM!oX<88#+_%M?;@J>WXFGp+n(1rTN*rD1PE!Ax!N=EfJ>iZCf_acWpd?n$5 z8N(r8<9i-os%hfhHXRP+K)2dO3c9r*sP@=TU}>w$)E2OIXDWLq3L)bS^3t1dGYxJ6 zM7K4F{GDv)YU!+%sk{XRNFB(@?hXyb5SGEpbYr8JX)=)y(Gp2Y007rOD8I*%$eCE& zKwXTrqzX+3Tpy*IlVKm8p65Qf@dBrMm_T(~<@HuDKx*w>OWX zxUMKIogUwNS!)@`i7@H%NtGuWdMW$#1n~G=2nvt+8f1C23WoZ{Snf<%%5?o_OOdwq zedNo_ArD_uoDqR=`jN}#xICsI&f<~XhM zOb@$)XeUvr-&&d37m8>+itI#vWoX~GM_eS*aeyr3a{FE%$nvBw$pxUB^fL~WeGT<$ zW~S#fcAfGLu_|j!B38`RaaJ2Y$u_X937w4V)V67^61oz_Fz)-&n=I?aTI}#X91q0T zm$T>L9f)jBqJh!Ph{f9xZZv(Lf41^>o;)at@LN43ZgY3KGpUF$6d=HzJQZvJICvSp zakmo@Gd8HAWf`AE2P((L_;nG_Nh}XFLT946j~{I^dOdO)I=S?!lE>qL<@4f3wURSC zM}LKv2^g0#U+ePe;{sjQzQM8@J>h0vk8+pOY+E4 z-;9};xLXbg(#iWss@uglQDU+D5LHFR&5fBCeFTj|8~1BYqw3jOA1_^J8`A6D$Mf*@ z<#-<6iLA3WQa+vu5$VWD zl%=6zW6alvGDb3MBqZ-ZQYrC#nWMAy^a)h8$6y$2rb7J;;8{H2D{;uAH7@~sFl}lU z*CFXU9`x|hbeN@mJXFg~U-QsCVu3LRAbYB9jSBnf9b>3 zd!oYn=g)N_v1#`iXy?)6amC})v8i5Gs}bvZP&F;l0l0 zccFK46bG?m3}P5@Fp3 zKn9Z3t{J@o;tDJk(Ugf`1<9G;cso_pHyHA-c=H6UdA>g2bK)m$b9^QdhCOQOQ_)j{HHi z-Uh9mo$ff*KPqjO(N|q&_PvUh3{dq`V{mWPw%H(^Gh?a~ab>MH(gS!%Vld>7L5QQnTh_h_*_2n#EyfY~%pzCn+ zPNYjWHW{=5u)CDr%BhLB(=;%az_W`fv!rq%OcD?$Ps92G7oZCl_Qu%dr4jgcXE^6S z4bY1b=+rkJe8H5bv1d(?1T5;Czc>le52-LgW_ngD9l(shrFkM`CN|?570Ag{g9wXJ zBwx-HK)Q*qbdkH+dA7{DaotEg%{KH6K?-uIC9Qh8R zrDroitp}=wNrnts$e7tbf)MyxpFwhRKM<3Pskjpm8<({-h=HDCL6?{2hax9vu1gS3 zWm?OBcZ-**xZU%Pcm>eLrSk};_JzcWF~wyW*HNMCz`W|lcQnMwV~HbQS&rx7ok`4) znM5#*uKvFoygAv+_5QnDw(!4+jIA2537^g0HrfCUXT{+7J&37y}BnVvxuRyL0R2f zARS04-%O_xUs7t_-IhF@itJcHpTCtM`4|=tOeu70*p7TbIppD;Np6^Qm!UH3nk^=F zM(Q|w$>SkGnyiNFI_m_4&einkxyBHdnAh>$GRfju2s zHAd4zwtPab%^)M=8V&|yxc_t7Sx6N@$;ir(S0A`I)W}?Y8|1VI8;H?PLb z&o>yCp&neft&iF$t$W;SV3?h?-mDq@=RtijKnAHu=|ufZh&0X4(InL%WMiN=daFe0 zErPeoxC1kvL^9LGUgk3{kCq*!AGjYu>ZG!D84v7K(DC0?2-HTrnS#X@xL2Q^L8>=< zOfpm@B@#4#joVD$y18EMHf!F~*E;o)uO){(yfdjyOv=dFNRMzm-fA2eoYeK#jd=iv z&Um`ZcDqR_EQXW5FO6JSU;l0fx)F}F(|1O9rh))?eub-p6v80Y-C8r|a#Wg3+w?`# zOaTglKu)!|k<2#n%E)Ifu7)&$$(R&al_lds4uTGbdT7 zik23$sME`g7}x+Xk>cZ#uP=u@yfYEfKqEzsw9A?B zlSvzPp9GGLjjY|09~;fkAb_F8WVQH4q>%v5i}A)MKL6Kr&0Mldz@Qm4mf5wYTR#xh zpHV%=bMkJ$^tUpk(g}#zy=!huomSbAo){CPM(L7QQSLJ6625UM2q?8@4EtV7Ncn4! z)|xfjqUD7p;mJuxoh50^N?S&Y0NJkJuTFxp8G8^dH zrFX*Vb={S8@EG#e9p*LPBVR$zo`-iP zyje2Af~N^N0ianyixV_HOf*2oc;tfBSOH}m$^vlKUBoW;a2&oR!W#^E4S47q#@ZL# z5XO&ZXAxJ9{G4Yw-gmuwg~ev=dRCxOeYsdiSKRWcvrL;k*u@|KGW%69;W)`-=IpB@ zK`!%n>BaeL+f$t)jI_^irH^-}N&1G{dTf9dRx!hR<~8cFHp)`%W!n7lq|&_rQLASq zKv?PwRrk?m7lDA(MbvM+HoCUK%rW~~#i{YY-XAKyfvZ9jqFLN$sp0>RF4XXT)d`&r?hxaB{ zV3G4P=UwMaf1S!bIJiM5Hh10c>I5*`S9|1&9qAq!c@mugzu*KH>|aOA7W@jSdU2|(Tr zTb_Ab_u5R_8I(5TYMnK+S>t!Ut;Go#Spj3a!K{}l*^U;-TFkbcFtdsBTv+ocBIS=> zen2XSQ?fP_W4!4z`bcmlYGr9xPUK~TR{}PEpvrlA8tFgkUcO42Rn6@~_0U8phVR38 z0;Xr^1V|4rfkWOB;R=USUfMp9R#v{N629q&*-of-32?^LE4(74iRzPGjxzAlfr*3m z%vlzzQaS3wW=ULwh;iEUIz0oK7mA>JZ@1pBb{j~cVd8d@)p5=?AvWa7o&*5G12z&K z9?yR4noo3nBE0U}3isNB^o_KuzUgV68QFxgOxd`#B$$7WTDrK?;K-MfLmu9lEHgR~ z$j(8}uThrSpKIxN?@ePhEWCeUaanJ|VCIK5fY+I|U`QEE=NoBNo7*q21KmZ<(_ zA1!dD&6BNl304*mo?&9Y?5qb3bVB12mxRD;$tTzo)IK(cDHjiUC*ZOE9s0f zt$oo^zypG6sVnFE*k*1Wnb(df@punNJ*s*l?MIo=ynAgX&uthwWM3t`*w|*g3-6x90XyOVz0G?Ss`E`Uu!$@7A}>tho2~Zh z$k&rY9^Rqw8y9NxJ;6GjjgwmU-pXpHz{7RjRMo>(I%U);X>V)$QBE} zx%h24kyz8##Fg)f!+o`H`&M{pqQ>~M_mTSOqP9ugTG~gxrX2F{4uwIY3|E|}8!?BN z%$(>ottElX1!C}<(XOK}vDkq0WRnbh(%`mb_L{~w-zV1%dun{iXvT8}l02;7;mrWP zq(3#63fCu#qZ|SS2!&5dNP@^rq!7}yfFxU8DsQ$=99Ncx4$tVOrTdygHiK zRpwrQM*4!jCqeZDBQW7Q;d+At2v=vN$nClzbnXgnOjx*%Tz@Ag0LvM_XJs2^2iZLFT~vPNREK z5aVwb6nYl#3jKKY^*}A(5W$NXIfFKkKcr=(PZEL0>_3ir+r;OR03D0aJhH3|h7i!* zEMX*psJ!b3zUpu1*3UdHZ#gAgAZOE~$#BmlaE#QLH7i5IB_>a7a~$+(|@ zh@D5vfu&zhT?wq6>ha2FAa4R`ymAUE9}nfM8;9iOH+fgioJOUc<(AMrjjFfhlh>xl z{qjyDW|5+dYoo4DV|4a|n_?aL!g9#NI~E>fJTBQh7hrpS7FyD{A?iD0m;fm8>#K~R zfw*$rg*0$@e`Ay{kGlc+=JhnM}x9Tjv#0&328QBDD6aY$^^LbxV z0g;i|NID+Y>zfQ9gx6He-r7{>kB1akzl^VOuI-g-dW9iS zj&#N%@6{}vBIk`#{ZSrvGt|MzhwKC zA9BdUI~OS!^sQM?&%g4mO>tD3 z8L*6*ZQ_pWOi0SIcjLNzuqyH#zj&EhmL^meWgh2ldX#qpt?r$X$gt!m_sHo%hyeyL z*0I0Q%uA^BX=Y91IV7>8flN_+0M2UzLNEm$5NB5bbhfu5>STq09N z&}Oh!U-@w*hY$xY$3i%5lRv0wrQJ2{Y!4`L06#XSb$v`%d-i*a=cc`Vv=_TM4`q7W zqHnz-J?hB6x$+j-iNu)fD#@y}wsDVqaXIASoe8fC4qIK$y>IfpyExd8tjbQt(HKawZz0YihujIniP=7V_P!nYx^l?F zJC$z8k}y2;l*9>H$4bPNQFG616 zcKvk);4N*h>Nht8x2E^uit4ea0NGcZlFXztt@Q(F zjv(qj`78HxTlyu895$e;LXm=N(`@vm=mXpKjy!PfGitS1V+=}q?}YonKIqc3$K8#$ zK^!ny1@%@^dJ!^^nx$yJnh+J&F~c&6c$F|8`J!^j!#k7Md?&N-Y1sObg3*y@DBDeE z5|5|)9FA}Lt$ued+ldA>^7D4>r2dp~zuicg2VX2!+GJir=@yC%_oh^kJqZCubX^`H z2I8IaNZ@h;ic-oeKUbGJZ`Nbo#=moagG1t$!EG~chZLXHBcznDMRHL200jWkb75#| zddpCNL^JnI_Dfl>vOVDZX>aMjL{d|i3KLY~>Jsh^yhnSi-_j_Me&(G(DbFHkd#i(* zclo2wsRzdhU3VZ)qqw!5Cwbe6dMu@3*6~l-J|5WA0XD~1`gr)SH9vw6sAzg3u znWaixGW!%y=8-QW=ZCT0nG}H34_oC=_;?t%C6XAlAv3ba(4|-lni!FS%6~}7AKo2`gs6w$z-rzN*coA1<-KkaST3+z>Hr#cy}F;gDD z!Ua*5^P4w~%eDXqfwa_hZ&9k>Oz%;oZT_0l1&#F6;nwt)wsNV#BnHuZsg1OAmB|Zp z2_y|QL+1k6&~f$;pBW}831ARq&=b)xpgIds?$KP&^J7md4atnTu8N%d)Z3)ztjk}s zh!0o7@4Im&nsRBtL=`gbnznYgIdf+h0PdMO5x5_dqF*280Q~hBNz1a&P$T6fB~*mW zgq#g;#M_&(k9>YPo`-iPP9rPVVa>|uTN$(Vy_Q{S5a{%Fn=C3Dkxvue zUiz9cmbcxHyXRE^r53(~h;L1ZopLO0P;tRp#r0;`opiF)VS-P%jl=L!esS%Tf6hK# zUv=#b6hCY2Twa6B#%oR&^&Gl!JiorEI7=Y;RXC^rAWI5PM5gq$x-!<^Asxu6@Tff( zE@eTI((=Txi`vm=&so9Y#_$z@8JG-O#~t)bE6%6Ls7rrr%M026Llq~!K;zkqoE3%P zyixrr>5bOA7JoBFqE31{z<}1L(NeCuet=v0PTVy$yA4jiNns`))!dRXueAkyquX0{ z9Zy^Q0@k%nNHZ;qleZ?~ zr1vNC++l(%Knmfx+9ZNi{_K??h?P57vhpSC-0kV!fW>Bg1VI2-OPw%Lnc!>IA~e$X z3eUuD22Yn+_9&;08rNY&+4nJ&9<9__Aq~rx`iF_XqBOH$h=!uhTN z`#B!&HkLiGLoNP;9QpN6GiKTr5smZqPmr~b;lh!3fyIN%#`q<6BDC4bx56A>JRM48;z&Fj|gbx+V!#B7}Ls( zarG8f=QzMdzjf%O0oW2x)_r9cIP$*bkcW3Byrx$J%H)7t9td=;L(Fz!(;7o~e|?ec zW0Hj@1XNxm?=9kDIn zxlG))zVI|7fMt*dlb2bR`c+X%Cjg0}$~EiYyoAIRMd7T70|R^Xfg*2GJu2J$>qAVD zdVDafJTE{ev5s|Iw9Z=3X;QIx?p(u{_rNugtvGmy-g+H2$w+amQKn*?G#V-#+Z!XD zbKF{$^T_*@v*+O*3VR;HO3C1ky#w<`e*sF@xx?T9N{CPiq`P{7Co%;oS=|4a>5-8K6qr5wkz31;j=@=zm}tK;c< z?=mFuDy3_oMjOs-w1MdJ=&efnl1Qk!FanIXcGk^?)N5V=(`-+#oT^%@?xQ<}7c8pZ zxnZ5!@TRQ1%u;0?wZ?re-mr1WfWw2lnX2xa7;U1~LK58@*w4aykLvVzCd^Aly8L;T zE#9*n&%?VHevQ)A7}JbuH@o-zcMR&68w+7=7O>Mn#c!_J`uQf@G-71Ao-^8YS3R~y zqc0zHP!&)Hc^wi^9R(_^XQiV?OpU79T9yh&Wg>9dnDM5otKI=L&U+q~$B^WFnqUWg zdbyLEifkLODtxVa0<}rZD?uIt@V%zbPPn^9yJVK6D0?hMME2&mWYy2**b>i8k?h@= z`g>e5>FCCE8;7awBzCQK=)C!wui0bMJkT^d|M8cROH6q0z|tJ z53jloLSely3+Dj${Lv5lkC$mn9?!%3l|vrhq41`wV)L}#Z#1ilA<4K6a&e-pzz&DG18>z$DTB(?8d(FL!)=*?fxS$OdPBuy0NBFV%z6?Q`iLRx`icpwuOF- zYFTm#P)tPDrHlX-Y>3u=DJMXd7DEF6TUG2qlL1a~pLLdV3S%vd)jEk>8|5eYOtKO# zX=nYMl$sG9q~LBjt9sy`R74nWdGw8heUCsCYvBw&0UV96vhJb&I{I>1eU4j1W`Pq{``WIcPPDw#6H|gv${zEtQvL4UF`;|i;K7cIi z{62>87=!UWeQo$tje&55GiSn25;@A3kwkW`W zjS(x@Mb5rKIDmWoIEmV9xW?+s9O_1|M0h`hFQ&CZ?R8O*=b5KZKz|7GNP`k*1TfOaz}PJ>mz&2qmr4 z(JSzdZM4$+rh?E&_4>HA>vIgeNSNqjk4OAW`-FAh6E2R!L`2C*QG@P{T>6qU@N9!1 z*Z$~_b$a1#3gRPF=`*fJ75Bb#vW}nmn8DD&ag6aq^;HoTXD}cw|rp>7;5liZ%W(? zV2^m#lVW`?WqDTJf!vokk=XDw&PUOOEZ((FxD+@mD0>@qQ)GFo z(({9A=d72TC5XGR&NKaPBY24-?18ms8G!pfdp+G3>jAhDi8h{j$?$j{-me_;@PTA; z-v$Xtf?zpA8vJ4!>uJCe_sC5Ptj0T{-s{i-qX%vlS@L@$gPDhku7tWshQvUUsG7~sy(Q*bGVZN=#r4C> z!?hF&5JH?kOxBhnox-7&Uv=1*>d5<$LmobegkkC9DQB25CGh^)qB7?CMMCmi^ttcG z-c^44IGLBAL(Q53%$AHeZ2-G9|8<7DM$a%F^iy;>t zk(K9~ba);vH#5NQ46~Y%WnIAuj<8%;!kGc`6Vs$a>I|x5C@^Kc&q7zfpn6@H)4XAk z4-wY+9cJ16ANQr4S_a<{lB~1p$EDuv*9MBa&bE^@wEhC;`lvkt86eBt^fJz=^3M{u zFQen`V{MU8jc=Fp$frC|E#0Y}?K~Ws+ZB4eWm5NC+BA+Xx{(su|GI}q*+7gn9{)8~ zY+DFy_Brir%#rsZhdg{RY2y(^+vh*_QZri((Wi0!DRxh7lQ%0OJn_$kE2zcTf1xCg>)aKVfJ6J_B+-6xG)@u?J zAxd7lIrPIzV3Lq|cY{9+dc}KGyz4A}>*67xm);8cs~KYfyeINbq7dmcz!hhKQ#=FZ z1t|v#)h97F=@Z-sBAwL9c|24*7{`4$)$AmiPUOcaGF;*siiza-@**)g_MF;>+lu0t z3=><)+|(iZLwU2TxJ{6<)O#gQfaYBj0LUH#3ZdhF6lk+&B1%i`(}@6kJo!G2F&_U; zedoygmO~ysoYdgGu4rUBg_yS>=A?tPK2-^f$=U*lXo7S90YLWOclJg?~=BYRqnB9oNgW z%go`%{U@KO&U-B~8v)g3jRz(XhP{Du(x)=}R72rg0yK`^W?pzBNt0CcgHAvPl=`8b zd^*9Bfe5*IZc+cE9XMN{aFDRvFyZo-xDD7&l5N zftF;Zr=)^Ci$~tS9P;piq~1i(>v=^RCy_>XobCXZK#ZTz>}X~pbNY=|*nu0FlvSK( zK+D5@E;j@yy$@{TOv`)|lT2R$2*2722{F%hhRqpEW=fA_2W=)6TkFdBa?zNtDZmuy z7HM<=NnR3Jfs+(1=99oA;Vph4wW6|*CGXHo*zTs;tvy``14;*(9fxj6EPLDlO1-u_ zQ=`w00}PXhuNuhd8NhU*$d5Pm|pmDJ^sm0k{|hya>&C6k}=k34VsK0yK~1){rFPctKyHs#ioJ(&p#0jIu9)Q8};FRqUahQN1-H6TlSx zQo$d~kqSfYN(Bwh;8-yVd#Pog*as)0@#C#BVdF8^B%@$>ujHaTlfX9Sa01BddksL~tmq5loffk2r zfci&~1jxNu9C5nuZCr5E3c+EN1cecOD1XCNpK8B#f=~&qeN!a}!hP812iIh@**-ZY z5b?}i$3g@ga7_OR!^Oj4BGnRzPsLM$D^bkWi?JuRh0bRFaVh&w*~d@?0-o{j(kKp#NBVAh@p2HSeZU=$hI1T0x??W_7P2c`~pfIN3+dXzb+lMKc5Q4$Eac+1W z2yoMG>5#w4{%&$oZffV*4I4O4dyn2ZOLHh#8*pdrr3iVuzV{#Q-yI0<%EttQ<41rM z5{M`)ysIp+#56kD0BNkn$J(-sv0gLsn!?Qq?4t@1fGJ)psL^)F4Aoon)Di^iKzUOb z{uAAL`Mt$&;Lo6)+yUCb%+S)_wK3H})7jBxyk?{??g@2cSPX_-hDw8P&wgz)rWN7D z7VzWZ1-fsf>!s(nW2Wo0xpxz-p3zc7)+vuMc5iULSAXZ^U(4voN037vK8RR)31W6; z?HS78$KduhM~)fmJoiA%Httn2QMPe6N-B7$VZWm@Q)8n{=vjF%cB=VOrl-8Tyv*`g zBLH20Uy@7BZ2UdMD7t_-r@=GZvfXSg842&|0}1}_<*{(i$N8HKzEZvo5cuQKvhdze zH{vT{*${$t!dx@bR>QQZG|KbHeH9tUcN-W@>)t7jX&$|9g~l2^Wfna_9le@8xb3kOmfEvdua;#Tb@O8!t=-cwtBzEC8B%}6a?y1|R((^y(I<<{LfSv;Mr?%L z*qkFDK@NHNAW|SWH_(aqp^g4IXx-|)0si<*fsg)p(tcK1aWV^k-8j~)aQl=?bzPUI z!I%8&e3u>*n@wETcka9E`L$Ab$CCzHm4Y>mVt)xHSKzldY%#nH5WD*h*3!XmJ-FsL z%UMuq)%dFa&Fm0daClRxHM~<;KF?%YeR*Hrh(wTVi}&=5vNA+IIO8n$LkL;R8@w-O ztD>c zjCx7_m=oKuO5zop;FRH~8ICG18=#itag}$Hu_eO4Xb=p zOUDjCbB_a+m5-857OQGLzD+Tfe1?A7Pl|LP$hi0YArusVE}t{6$FLxNif~`s~_gxPm+6%*ZK?b>;(|&5|X`&-}_v6mQspP6F2aKxODN z(Fhk_lJyn68IEUsR(86)E8dg_c%MaTYap)$G90+-J;)XshSrXVFxJ<7gZylIKF@Q8 z{%YI4Z-T3R+^QKO2yCyV4F_xkBBVhChC)F-Hv+l}3t&UMxK9;0mj}j3fr7!-#_*-w z+#Zp};#Nn2yMVkC&zX-UEr1<&^T>73mksreCnF_OqZ!_U#5gI=u^e04pyk-LONFyc zul3 zDhx{vd;=E4X<`IoK)<}O@!~vOfVA7gW{2pc8$ULTT8823tb}9MB0IIa;R#YP!%ifF zl8;fo@wL84uRwu!*pGWyJp9310u*?H4k>P>ksN|&ospQ-g)^?8z(K*)IXpcxXq)+R zrTc+WCR!K9T5xd3E#5^f_-o7nUWJZ`rnxT^J@$Yo5}aj||3s*@8nGF4^=ubcXveHFwR>#lciTb%^0U||+?NmdxE1eVH`GYdq`INDFHcAyw5G2aT86*xk6mxSIlmFBLr;EJA5T3V zkL?3QOW7`B5F_L7wOalJ`>Ka61fq|FBOgzW=ivhiyVwKF*dWym9X0umxFb@zEuU{D zkpDIBFJiaXVf12M_$g1L)K?WOt;wBH+<>Ej)|5q z(`Ir-W>#u&H#<{fr!&bgraOZe&t=(x%B=2x&Y=Q49`$X6y*V!>jWq?^PbA4Uot3O88Pf9%q(>nLQ(D#jf)7 zu^Tx?nONIfLh!H*ionvHg`zY1T;`wzIsZ%2Mjm2+U%(0BAYuA6mK~fmZ`Kz@H>G`funS z)7Mre(9HIm|6XS2Vp6q(Sr)s)Sgdt{1Wl8fH#>h!08;_0Z-R}79`~At37FDY&(p`O zC%^V-aDCL6h?a|29F>Gn461-?@#rrjnucSQcD+8Dc2}pNA}=YHcaHixTAk5--Iv4l zR%f+*8aAkw8SnNM9goAuma}23TDP89!>`@w~QapfLuxK(^H@YO)qO zEn1S3#qc5UXDdPhZjBz#lsd_*{H|u#X1Ryqx5hiz@C!QC2XLlBp}!K4PH5CV}uBPW5Nr z?wOV&IaSxYHHSE+1jyF(jnZaPwA(zn0KO@qyn9Qgdm@H!gPi?RD2Y8w52?;!o2J_ij&jT3w2- z`qzD({XW{OtWUQ!*K4Se#kbPulZ>RVGgRJ4R?nPBMMh1v4cN|F*^^XpR)C9iIx+af zBGjz&6=EkjdyxTt&6M>H(GN43fT6vele*&F@z&clb^TB>yU}ND^mwKvwl zbu?~Uxq2yY6FqVcdi{8nwK73w-j*aHiA0q|0K5o-ZUXI_A^nzV^;@qPd_krH3n^pl z#$Ujh>uG>>)bC49y|tYffY#Q+T8CMWoH zsGo{o0i&&;WWwkD-S{8jV_avIatQpz>*~lyltUiAvN(i0QjgAou4Dx3$A zJ-%e(<2?J-uQo>RteP)mh10{HX%PuX!oz=Yk7wbiev)J=iK!Df`Qjm)g;J7gM8KpU z`2;g|wR_gW2oHKP+BLjJ+{E{r_PC!Qb&Wb-!)c#!U*m7{@J}ELoKovHl2!(qWp<_k z&W~r^0{7qAXxW$v|4=VO9glb6yCa7@e2c{sWY|q=vPRCxUpCd~-?|?LeP=Gq=$CbK zn(ftWA8G)-rN0zHFYoPaX}^9<>Mn&h411hCsBE)NvrJ^O6J+D)Wbh?j*K4l>0xY|V zOfHO#w6@in%erSJm$a+_BLO<6Bk7sGt-#A;*5Vowk~(Qc z^g$E)$amCf5SvlgMY7Vhjv_MN$qM`i&!_MYllx;H~km*+o66F6Rx3?~WYu@U50Igt6`z_rv(-*8(}pCA!QZ>&`8QiXpjH z827gJJFmSA5ItHzf-~7b3K=gZNq32KquwRL2iXbNzkWfl4Dkpr^-BfHvh$4nP7SWx z?iuJH(lE(=Q%UYiDS77Em!hsRJRZHub(w(9cl|)(x4^J&l%8+ zP`3b)b@Pf~6+g&FzGjHnPcbra8VlOH`N&~<*ad#aRf0byWm<;-tZ!*Vm@ww?8w6t5yKZfh<|3^e3|Ml{ec9lc#3~uY z&~bf*&zJnIHVvxw-OERv+j*vP)6Z|$nndbBxIkecD2QeIg&iY;kzVR1hY=o$r?Gy#=rxJfXatW9MOv6a8M^L42C;BUl*IM0t3y$v36U(xv;&e zsbcpgjp)_+d@>Gi5jXrB@l)nK&ls1Up-hNSj(?+X!)!ljb>zD$hdg`-CET9;ftRP$ z$B8gzL?iTE8h(tmb0f1vHg^E3&6yN!T;qI8fsyVs;qq!@rZ)4Q25qrbRsg=v>c$WK zNv3z$VA%K#A_D(~5$#~ZM@^}Kc?X(1>7YOB$=5Kubd_*KNj&ph+}r?O+vFB;?z{^D z=KJM?k-w53P#XIN;fEd z1e{#~pnPMju?H~HMEk)v54vEj+>|tre-CnKhcp~XIppCxDo!rRIuCPEO9Wx(W2WW= z=(#3(#hN?4jiH~6n;C@_O>)^I(qDlKfjIXOulUEanMbTuMvV-ip6VE1Z4f{F!qp;+{-mKmy4NI4hJ_Z_7Mw!9pl9g#D=y^>~z>H^%_O>Ze zD*LE>rxHxo)1&7Vzs7&{huzY}k?*dY4U^w#@ihL|l@JIMKpp^IU=4==_Z8!rKyG?# zKV%n;67O7%Q&Nb61rFB)qagQynYwiPtv)B?fmC#3khG(IrXxr_sD3T=UY&r zs_9rZ>Oaoq29!WxUE+%Gn;Zgh@AJM$D|9qvj_+=rXe1+Dsg~;8tp0Z8e;jQ8BY9|zB#g$kXuxs(v|Alni zRjB@Wq&CHmV`$M+sgk6gbp*CKm22?DDwByvpxg4)D*<*pF4{~ifL~#L4tyBy&4t^X zq)pihUhS8qT)xd6t~1lq^Z!DDn(qbR{!?SFeiEuC$-2gM&A0$}V%xU{pTVOnwXx_p z9y{5#MGiL6M;rFF)3^I+91Q!yF8}s39J!pRN^ch&NCx`QFacw6M+WC!we-Ee_<0@k z{RfGIu(yk(LaZ0qV57NJg3IAO0_RV3p17EheN%j4OGh%>{^3%`9(VZTvNQS$q~k+I ze-eOpHxB^b-(gE?Xtx$|dk`2PTdiAj3sE54hk@n=+*@jW9}o9!`FL&NT?Tf=(T^?> zyJS#zLOT`Tx)gr#GRKX7_C`ZBy==~iI=r(yCAZ6{g4vwj5LcbdO@FUTn?%4 zLa#%^hm$e4IO?SLqAu!(E6O6II_)_$Dq*v?8SN~$mQ}29*0JJ|5>|?f*bNw}xw;@V z`)qwZdwz>gaeTiifC(&h!Hg;{lqC=qjXK0NDDOTspav8qFqXMS-85)1qP9ZzRwt9C zIdYPk=IpoJ3kC_@CPERid=IGt)+Qs^B!zo{dV@!>uC!RQ2mSET&h5$IB&6Trv&dHS zGIf$%UP8FPLU)k*cfJ!Cxu&o4w7U@C*`(Q3g(#t)IxP1->AEvO;jQ2`LX&51WHMJ% zp_aeo*tz;L=z~X10Bz`$bMVQjV4pby{YxmtOBWeD5a1wR>(6%!dy@;rqcy(?wb2h5 zpi@M~GaWDhh23|#X1mJweVSKU=69Q)uF4qPpo;Vofacu&@wUY#&?JK1cdivOXaBch zXOHyNJpKHFqx^u-#$5MRZysa58pd82yi4kRa# zmaKk?>IHj;dzN$eEjYu_-{s)SQBq5ljWgqqN**JWnA@WkoF&90B?Jv2|(G-bGq&7$cEz0qH;L78LlE95>Pt0RilG+bKmd~BZw zd;(i(>Q}7Qgg^iFJk0p|+y)`Ub+RS!hWPH9u7}mHC^?e+v=LeKwP?g;a|qR@u9(1- zd8TQze5sDcQAlp2OWy^giCVC~83byd&VsAZr>n z$O`u0z#7vyRR5FIHUa4!z8KdtH(VWgmd`eYN=YTu>9L|z)f4HCEM9vC($h|g#&9~; zeq;K*jeHS%%3kzr-eUy6(cs~)%%Y=0#s?IMvYb7qg%+nVlvar1^w0yjWsTEJxAR7A zUP}>a4(?`Y080P)53H3OA^}T-=7%@uG;;o@g}>6bSCDhWrJoV zcB7c4)*SI7#6sGZFPiuy2!apo*&a$+xuXj7gDhlaUqSp>1f6Ri>=^*7iW&`)-@YhK zNNc`&5-i~w>Sjk2!9V-Wiv(vSALI)J6o82@Ttjs6{#u0Vjcyvq4$TV`zJFBzzfGql z66bgmK0NGNScE6$hmWiHa9+Ns{$|CV$+twiT?NTlD?WoH`68#U<4?J+ArpIe7nu&I zj|D1am@H@Ru9z@min89*bK6WBM zj8>8+*Hy-4h*@F@R0+Aai&CDxV;f7~;!`5ZlJT_IbK&AETa)GomNX z?`-Qa?LgNta$s+2F(a=wcg?z+BOVqj$R_5H%}B`<>ItII`o`F}?f+pwJ~)oo;1B1% z%BpbJ#=97AaRnzyJ`OHgr~-@Vz}etqQ1k{h%$lua!3i3sXWQKnSrV5zf< z*3Fv$$|te$`gX5D@-p0X-Q-hr?O$06V2P@Fu7Ml~f9~47r@_W4&mg;(Re=- zy0vM*5mGOjGLkqc!&eq^dUedFFU0c(mJw%aE$5K4aUZLRuH^XR%t<$Mo`TMV&Y3J1tIYD{^}QYJPteoN zl8xBT6FEUgu&pEBoihmy+sblZ=$f{~u!CdfBzQ|-vgmQ6dIXd1qrE96J;>ah__Yxy zP}w5mO7?`(WJyC1dq?z9=O-g*R6u?`sd1{v@e2o9&1m)&SA(FtR$jQ_-)VS`noQ*ERgk;2yGf!`-@LYH?NK0eUqazgtEa`ZOx@BAq>K_nk|HjuF2iII^ zvoO*vRdtJghN+^uH`U~ho*1YP(ATq88B{64PU!SLXSA2dZU?7JlbacahE_j(wwugN zN@YHux6bdYwGyk<3nL0rtV++%&zuz*ogkFoIl5*daK*luvys%NimBz7LGrn&D$GmDz>wR~99p`oY z{2vt(%_8&fv`*OR|CPwhT@ zl1}K+jzOWW6Yj}(6)`q~oSyH|AtN%wGw>c;vOoL6>zypC^2Ec1`bcl^)Ve5ueM@2V{k=-=r+`ElMGjxcqaTXi`i|6j>9)1_ zF^Re+I5RLR?VG}4b^2${r3}1_$w+7H?@V2cl#K=TGE6_6Mmik0C7%Pj>xKS3A>UO$ z8I?S)@U^}*v|(*FmBQ?&(wN@Iv&3mugAO(cn8gy5!3G6L#F{%UhO0rbdd|5!!VebB z6p0D{1#1EAe+R9xa;x+~67XO__YQ~I=8PhBDBX_FXvW46rZ0`iG| zH9;nSU=Hcwv^*;T?<{o%DdaHP!gux$v4Rz;{`nQ{CK+0XZJS}slB>hLlfhPh#iJ#d z;pWTzI^_29*!BQp$eW=*kC_|&xg*U&qna&GQV~Pb-Xp1Zac_!m-sy5S&AjKf8EqnD zXTu6PWm;}1sp=oBb7&HTnv$D&<`-Y&^@$z5kpxrlmc2&GFlC&)1Zi7)Y!`Lw{36kQ z2LsCjCjBVim zMNRHPxRqlnUme^U?z_Nnv#a9HB#;K9r0GBNP>=$-zn%Ak4mu{F{b)q#W2tC6Anvmc z7!h$n(UHod38tt`;u2G$XwlWSCShq!gqU|NeO-=fW2ujad)wIYpt$pBuENH4;=MFe z>We?hW|MkEo-HL@+$W{qQC$fjf&aZ5yTZN;qOWYJkq|on+!2&@UducDrna~ar!lVg zdtm1X4A_j^rJaL|NBo--&{1SIu)(h7nC956@Z~^1?9fFQh9HtQam8VN6EW3h-|J~| zn9+=ey_GLeQ26@(JUBa2=Oknxw$cr8s2y>hs?U*MII$6F)CiHrTU<+qmB$Mrr<5PG zRqMpI`;rS;d?$d~pRazwg9#mHUgG~4e(n>UxA1bqhvm|;d0s{?J(iE%k=Of^wNLC( zyI%+Sb;Kq;r~^h1{FErMrp$Dda^h>^FaF##NrgT76olDJ*(mL24$hX-`*6Q{rSo-7 zN4r8y$slH&KRbT5idhmT3$0;+oDAE`S z#;mPU^j@~+jSxUzfse4K#77P(P%>Xzhb%l~-udK_0ng!wZo)wHk2t|GfHnHEqMe$I zjry|+0w1gSiQUIRnc5o84ujmNVhFHM{f$%Q7WTOQtJgeh{RtqO7i3KnkWOd3*9zcF zkP{hL7oGpqb`HKaHGg&VsXT{5^m?eI?XY7=&tG8xEG{>#55pK%GQC$vj~Q3?#Q%?C z9LskSSrEWJB_Bjb;13F24rsqKrtqrDJ#c4S9^Glx_29SHrk_bOj8{C{`pfi~N-x;} zN227LqeO(^cT;+%yGXmUHsDID`CPi@qpmBTV-42W{|-sspU(exLabT4qW1zqy629m R-cyi=o)%2=i@Gi5{{Y2>OcnqD literal 0 HcmV?d00001 diff --git a/frontend/main/src/assets/images/dataset/textImg.png b/frontend/main/src/assets/images/dataset/textImg.png new file mode 100644 index 0000000000000000000000000000000000000000..2365e7988e0bd04c8f5e8396275049745986051e GIT binary patch literal 4991 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 literal 0 HcmV?d00001 diff --git a/frontend/main/src/components/BasicCustom/CustomRadio/index.vue b/frontend/main/src/components/BasicCustom/CustomRadio/index.vue index 0156a54d..7bd46252 100644 --- a/frontend/main/src/components/BasicCustom/CustomRadio/index.vue +++ b/frontend/main/src/components/BasicCustom/CustomRadio/index.vue @@ -1,6 +1,6 @@ + + diff --git a/frontend/text-tool/src/actions/flow.ts b/frontend/text-tool/src/actions/flow.ts new file mode 100644 index 00000000..e222242d --- /dev/null +++ b/frontend/text-tool/src/actions/flow.ts @@ -0,0 +1,11 @@ +import { Editor, defineAction } from 'pc-editor'; +import Event from '../config/event'; + +export const flowSave = defineAction({ + valid(editor: Editor) { + return true; + }, + execute(editor: Editor) { + editor.dispatchEvent({ type: Event.FLOW_ACTION, data: 'save' }); + }, +}); diff --git a/frontend/text-tool/src/actions/index.ts b/frontend/text-tool/src/actions/index.ts new file mode 100644 index 00000000..5851cb1e --- /dev/null +++ b/frontend/text-tool/src/actions/index.ts @@ -0,0 +1 @@ +export * from './flow'; diff --git a/frontend/text-tool/src/api/base.ts b/frontend/text-tool/src/api/base.ts new file mode 100644 index 00000000..10e7d939 --- /dev/null +++ b/frontend/text-tool/src/api/base.ts @@ -0,0 +1,78 @@ +import axios, { AxiosRequestHeaders, AxiosRequestConfig } from 'axios'; +import { BSError } from 'pc-editor'; +import Code from '../config/code'; + +// token +export const requestConfig = { + token: '', +}; + +export function setToken(token: string) { + requestConfig.token = token; +} + +function isResource(headers: AxiosRequestHeaders) { + // 'x-request-type': 'resource' + return headers['x-request-type'] === 'resource'; +} + +// Service +const BaseURL = ''; +export const Service = axios.create({ + timeout: 1000 * 60 * 20, + baseURL: BaseURL, + headers: { + 'Content-Type': 'application/json', + }, +}); + +Service.interceptors.request.use((config) => { + config.headers = config.headers || {}; + if (!isResource(config.headers)) { + config.headers['Authorization'] = requestConfig.token; + } + + return config; +}); + +Service.interceptors.response.use( + (response) => { + if (response.status === 200) { + let data = response.data; + if (!isResource(response.config.headers || {}) && data.message) { + return Promise.reject(new BSError('', data.message || 'Network Error')); + } + return data; + } else { + if (response.status === 401) { + return Promise.reject(new BSError(Code.LOGIN_INVALID, 'Login Invalid')); + } else { + return Promise.reject(new BSError(Code.NETWORK_ERROR, 'Network Error')); + } + } + }, + (error) => { + return Promise.reject(new BSError(Code.NETWORK_ERROR, 'Network Error')); + }, +); + +export function get(url: string, data?: any, config?: AxiosRequestConfig) { + return Service.request({ + url, + method: 'get', + params: data, + ...config, + }); +} + +export function post(url: string, data?: any, config?: AxiosRequestConfig) { + let headers = {} as AxiosRequestHeaders; + + return Service.request({ + url, + data, + method: 'post', + headers, + ...config, + }); +} diff --git a/frontend/text-tool/src/api/common.ts b/frontend/text-tool/src/api/common.ts new file mode 100644 index 00000000..1565d919 --- /dev/null +++ b/frontend/text-tool/src/api/common.ts @@ -0,0 +1,235 @@ +import { get, post } from './base'; +import { IClassType, AttrType, IResultSource, SourceType } from 'pc-editor'; +import { + IFrame, + IFileConfig, + IObject, + IModel, + IClassificationAttr, + IClassification, + IModelResult, +} from 'pc-editor'; +import { utils } from 'pc-editor'; +// import { empty, queryStr } from '../utils'; +// import { traverseClassification2Arr } from '../utils/classification'; +// import BSError from '../common/BSError'; +import * as THREE from 'three'; + +let { empty, queryStr, traverseClassification2Arr, traverseClass2Arr } = utils; + +export async function getUrl(url: string) { + return get(url, null, { headers: { 'x-request-type': 'resource' } }); +} + +export async function saveObject(config: any) { + let url = '/api/annotate/data/save'; + let data = await post(url, config); + data = data.data || []; + let keyMap = {} as Record>; + data.forEach((e: any) => { + let dataId = e.dataId; + keyMap[dataId] = keyMap[dataId] || {}; + keyMap[dataId][e.frontId] = e.id; + }); + + return keyMap; +} + +export async function getDataObject(dataIds: string[] | string) { + if (!Array.isArray(dataIds)) dataIds = [dataIds]; + + let url = '/api/annotate/data/listByDataIds'; + let argsStr = queryStr({ dataIds }); + let data = await get(`${url}?${argsStr}`); + data = data.data || []; + let objectsMap = {} as Record; + let classificationMap = {}; + // let objects = [] as IObject[]; + data.forEach((e: any) => { + const { dataId, objects, classificationValues } = e; + objectsMap[dataId] = objects.map((o: any) => { + let { id, sourceId, sourceType, classId } = o; + return utils.translateToObject( + Object.assign({ backId: id, sourceId, sourceType, classId }, o.classAttributes), + ); + }); + classificationMap[dataId] = classificationValues.reduce((map: any, c: any) => { + return Object.assign( + map, + utils.saveToClassificationValue(c.classificationAttributes.values), + ); + }, {}); + }); + return { + objectsMap, + classificationMap, + queryTime: data.queryDate, + }; +} + +export async function getDataClassification(dataIds: string[] | string) { + if (!Array.isArray(dataIds)) dataIds = [dataIds]; + + let url = `/api/annotate/data/listByDataIds`; + let argsStr = queryStr({ dataIds }); + let data = await get(`${url}?${argsStr}`); + // data = data.data || {}; + let dataAnnotations = data.data || []; + + let attrsMap = {} as Record>; + dataAnnotations.forEach((e: any) => { + let dataId = e.dataId; + attrsMap[dataId] = attrsMap[dataId] || {}; + Object.assign(attrsMap[dataId], e.classificationAttributes || {}); + }); + return attrsMap; +} + +export async function unlockRecord(recordId: string) { + let url = `/api/data/unLock/${recordId}`; + return await post(url); +} + +export async function getDataStatus(dataIds: string[]) { + let url = '/api/data/getDataStatusByIds'; + let argsStr = queryStr({ dataIds }); + let data = await get(`${url}?${argsStr}`); + + let statusMap = {}; + data.data.forEach((e: any) => { + statusMap[e.id] = e; + }); + return statusMap; +} + +export async function getInfoByRecordId(recordId: string) { + let url = `/api/data/findDataAnnotationRecord/${recordId}`; + let data = await get(url); + data = data.data; + const resultObj = { + dataInfos: [] as IFrame[], + datasetId: data.datasetId, + } + // no data + if (!data || !data.datas || data.datas.length === 0) + return resultObj; + + let dataIds: string[] = []; + (data.datas || []).forEach((config: any) => { + resultObj.dataInfos.push({ + id: config.dataId + '', + datasetId: config.datasetId + '', + teamId: config.teamId + '', + model: config.model, + loadState: '', + needSave: false, + classifications: [], + dataStatus: 'VALID', + annotationStatus: 'NOT_ANNOTATED', + skipped: false, + }); + dataIds.push(config.dataId + ''); + }); + + let stateMap = await getDataStatus(dataIds); + + resultObj.dataInfos.forEach((data) => { + let status = stateMap[data.id]; + if (!status) return; + data.dataStatus = status.status || 'VALID'; + data.annotationStatus = status.annotationStatus || 'NOT_ANNOTATED'; + }); + + return resultObj; +} + +export async function saveDataClassification(config: any) { + let url = `/api/annotate/data/save`; + await post(url, config); +} + +export async function getDataSetClassification(datasetId: string) { + let url = `/api/datasetClassification/findAll/${datasetId}`; + let data = await get(url); + data = data.data || []; + + let classifications = traverseClassification2Arr(data); + + return classifications; +} + +export async function getDataSetClass(datasetId: string) { + let url = `/api/datasetClass/findAll/${datasetId}`; + let data = await get(url); + data = data.data || []; + + let classTypes = traverseClass2Arr(data); + + return classTypes; +} + +export async function getDataFile(dataId: string) { + let url = `/api/data/listByIds`; + let data = await get(url, { dataIds: dataId }); + + data = data.data || []; + + let configs = [] as IFileConfig[]; + data[0].content.forEach((config: any) => { + let file = config.file; + configs.push({ + name: config.name, + fileId: config.fileId, + type: config.type, + url: file.url, + size: file.size, + }); + }); + + return { configs }; +} + +export async function getUserInfo() { + let url = `/api/user/logged`; + let { data } = await get(url); + return data; +} +export async function getDataSetInfo(datasetId: string) { + let url = `/api/dataset/info/${datasetId}`; + let { data } = await get(url); + return data; +} + +export async function annotateData(config: any) { + let url = `/api/data/annotate`; + let data = await post(url, config); + return data; +} + +export async function getLockRecord(datasetId: string) { + let url = `/api/data/findLockRecordIdByDatasetId`; + let data = await get(url, { datasetId }); + return data; +} +export async function getResultSources(dataId: string) { + let url = `/api/data/getDataModelRunResult/${dataId}`; + // let url = `/api/dataset/dataset/getDatasetAnnotateResult/${datasetId}`; + let data = await get(url); + + data = data.data || {}; + + let sources = [] as IResultSource[]; + data.forEach((item: any) => { + let { modelId, modelName, runRecords = [] } = item; + runRecords.forEach((e: any) => { + sources.push({ + name: e.runNo, + sourceId: e.id, + modelId: modelId, + modelName: modelName, + sourceType: SourceType.MODEL, + }); + }); + }); + return sources.filter((e) => e.sourceType !== SourceType.DATA_FLOW); +} diff --git a/frontend/text-tool/src/api/flow.ts b/frontend/text-tool/src/api/flow.ts new file mode 100644 index 00000000..45b62002 --- /dev/null +++ b/frontend/text-tool/src/api/flow.ts @@ -0,0 +1,16 @@ +import { get, post } from './base'; + +export async function invalidData(dataId: string) { + let url = `/api/data/flow/markAsInvalid/${dataId}`; + let data = await post(url); +} + +export async function validData(dataId: string) { + let url = `/api/data/flow/markAsValid/${dataId}`; + let data = await post(url); +} + +export async function submitData(dataId: string) { + let url = `/api/data/flow/submit/${dataId}`; + let data = await post(url); +} diff --git a/frontend/text-tool/src/api/index.ts b/frontend/text-tool/src/api/index.ts new file mode 100644 index 00000000..8a0518dc --- /dev/null +++ b/frontend/text-tool/src/api/index.ts @@ -0,0 +1,3 @@ +export * from './common'; +export * from './model'; +export * from './flow'; diff --git a/frontend/text-tool/src/api/model.ts b/frontend/text-tool/src/api/model.ts new file mode 100644 index 00000000..60bf2788 --- /dev/null +++ b/frontend/text-tool/src/api/model.ts @@ -0,0 +1,46 @@ +import { get, post } from './base'; +import { IModel } from 'pc-editor'; + +export async function getModelList() { + let url = '/api/model/list'; + let data = await get(url); + data = data.data || []; + + let models = [] as IModel[]; + data.forEach((e: any) => { + if (e.isInteractive || e.datasetType === 'IMAGE') return; + // let classes = JSON.parse(e.classes || '[]'); + let classes = (e.classes || []).map((e: any) => { + return { label: e.name, value: e.code }; + }); + models.push({ + id: e.id + '', + name: e.name, + version: e.version, + code: e.modelCode, + classes, + }); + }); + + return models; +} + +export async function clearModel(dataIds: number[], recordId: string) { + let url = `/api/data/removeModelDataResult`; + let data = await post(url, { serialNo: recordId, dataIds }); +} + +export async function getModelResult(dataIds: string[], recordId: string) { + let url = '/api/data/modelAnnotationResult'; + let args = []; + dataIds.forEach((e) => { + args.push(`dataIds=${e}`); + }); + args.push(`serialNo=${recordId}`); + return await get(`${url}?${args.join('&')}`); +} + +export async function runModel(config: any) { + let url = '/api/data/modelAnnotate'; + return await post(url, config); +} diff --git a/frontend/text-tool/src/common/BusinessManager.ts b/frontend/text-tool/src/common/BusinessManager.ts new file mode 100644 index 00000000..9dacb0f6 --- /dev/null +++ b/frontend/text-tool/src/common/BusinessManager.ts @@ -0,0 +1,46 @@ +import { + BusinessManager as BaseBusinessManager, + IDataResource, + IFrame, + IObject, + utils, + IFileConfig, + SourceType, +} from 'pc-editor'; +import Editor from './Editor'; +import * as api from '../api'; + +export default class BusinessManager extends BaseBusinessManager { + editor: Editor; + constructor(editor: Editor) { + super(editor); + this.editor = editor; + } + + async loadFrameConfig(data: IFrame): Promise { + let { configs } = await api.getDataFile(data.id + ''); + return configs[0]; + + // return {} as IDataResource; + } + + async getFrameClassification( + frame: IFrame | IFrame[], + ): Promise>> { + let valueMap = await api.getDataClassification( + Array.isArray(frame) ? frame.map((e) => e.id) : frame.id, + ); + return valueMap; + } + + async getFrameObject(frame: IFrame | IFrame[]): Promise<{ + objectsMap: Record; + classificationMap: Record; + queryTime: string; + }> { + let data = await api.getDataObject( + Array.isArray(frame) ? frame.map((e) => e.id) : frame.id, + ); + return data; + } +} diff --git a/frontend/text-tool/src/common/DataManager.ts b/frontend/text-tool/src/common/DataManager.ts new file mode 100644 index 00000000..7ca589f2 --- /dev/null +++ b/frontend/text-tool/src/common/DataManager.ts @@ -0,0 +1,118 @@ +import { IObject, IFrame, IModelResult, IUserData } from 'pc-editor'; +import Editor from './Editor'; +import * as api from '../api'; +import { DataManager as BaseDataManager, Const } from 'pc-editor'; +import * as bsUtils from '../utils'; +import { AnnotateObject } from 'pc-render'; + +export default class DataManager extends BaseDataManager { + editor: Editor; + constructor(editor: Editor) { + super(editor); + + this.editor = editor; + } + + async pollDataModelResult() { + let _this = this; + let editor = this.editor; + let modelMap = {} as Record; + + let dataList = this.editor.state.frames; + dataList.forEach((data) => { + if (data.model && data.model.state !== 'complete') { + let id = data.model.recordId; + modelMap[id] = modelMap[id] || []; + modelMap[id].push(data); + } + }); + + if (Object.keys(modelMap).length === 0) return; + + let requests = [] as Promise[]; + Object.keys(modelMap).forEach((recordId) => { + requests.push(createRequest(recordId, modelMap[recordId])); + }); + + await Promise.all(requests); + + setTimeout(this.pollDataModelResult.bind(this), 1500); + + function createRequest(recordId: string, dataInfos: IFrame[]) { + let ids = dataInfos.map((e) => e.id); + let request = api + .getModelResult(ids, recordId) + .then((data) => { + let { frameIndex, frames } = _this.editor.state; + let curData = dataList[frameIndex]; + // return; + data = data.data || {}; + let resultList = data.modelDataResults; + if (!resultList) return; + + let resultMap = {} as Record; + resultList.forEach((e: any) => { + resultMap[e.dataId] = e; + }); + + dataInfos.forEach((dataMeta) => { + let info = resultMap[dataMeta.id]; + let model = dataMeta.model as IModelResult; + + if (info) { + let modelResult = info.modelResult; + let objects = (modelResult.objects || []) as IObject[]; + + if (modelResult.code !== "OK") { + dataMeta.model = undefined; + if (dataMeta.id === curData.id) + editor.showMsg('error', editor.lang('model-run-error')); + return; + } + + if (objects.length > 0) { + model.state = 'complete'; + objects = objects.filter( + (e) => e.confidence && e.confidence >= 0.5, + ); + editor.modelManager.modelMap.set(dataMeta.id, objects); + } else { + dataMeta.model = undefined; + if (dataMeta.id === curData.id) + editor.showMsg('warning', editor.lang('model-run-no-data')); + } + } else { + dataMeta.model = undefined; + if (dataMeta.id === curData.id) + editor.showMsg('warning', editor.lang('model-run-no-data')); + } + }); + + // data.forEach((info: any) => { + }) + .catch(() => {}); + + return request; + } + } + onAnnotatesAdd(objects: AnnotateObject[], frame?: IFrame | undefined): void { + let { user } = this.editor.bsState; + + console.log('onAnnotatesAdd') + // + if (user.id) { + objects.forEach((object) => { + let bsObj = object as any; + if (!bsObj.createdAt) { + bsObj.lastTime = Date.now(); + bsObj.updateTime = bsObj.lastTime; + bsObj.createdAt = bsUtils.formatTimeUTC(bsObj.lastTime); + } + if (!bsObj.createdBy) { + bsObj.createdBy = user.id; + } + }); + } + super.onAnnotatesAdd(objects, frame); + } +} diff --git a/frontend/text-tool/src/common/Editor.ts b/frontend/text-tool/src/common/Editor.ts new file mode 100644 index 00000000..b1121ebf --- /dev/null +++ b/frontend/text-tool/src/common/Editor.ts @@ -0,0 +1,136 @@ +import { Editor as BaseEditor, IFrame, SourceType } from 'pc-editor'; +import { IBSState } from '../type'; +import { getDefault } from '../state'; +import { utils, AttrType, IClassificationAttr, IUserData } from 'pc-editor'; +import * as api from '../api'; +import BusinessManager from './BusinessManager'; +import DataManager from './DataManager'; + +export default class Editor extends BaseEditor { + businessManager: BusinessManager; + dataManager: DataManager; + bsState: IBSState = getDefault(); + constructor() { + super(); + + this.businessManager = new BusinessManager(this); + this.dataManager = new DataManager(this); + } + + needSave(frames?: IFrame[]) { + frames = frames || this.state.frames; + let needSaveData = frames.filter((e) => e.needSave); + return needSaveData.length > 0; + } + + async saveObject(frames?: IFrame[], force?: boolean) { + let { bsState } = this; + let { classTypes } = this.state; + // let dataMeta = state.dataList[state.dataIndex]; + if (bsState.saving) return; + + frames = frames || this.state.frames; + + if (!force && !this.needSave(frames)) return; + + let dataInfos = [] as any[]; + let queryTime = frames[0].queryTime; + frames.forEach((dataMeta) => { + // if (dataMeta.skipped) return; + if (!dataMeta.needSave) return; + let annotates = this.dataManager.getFrameObject(dataMeta.id) || []; + if (new Date(dataMeta.queryTime).getTime() > new Date(queryTime).getTime()) + queryTime = dataMeta.queryTime; + + // result object + let data = utils.convertAnnotate2Object(annotates, this); + let infos = [] as any[]; + let dataAnnotations = [] as any[]; + data.forEach((e) => { + let classConfig = this.getClassType(e.classId || e.classType || ''); + let objectV2 = utils.translateToObjectV2(e, classConfig); + infos.push({ + id: e.uuid || undefined, + frontId: e.frontId, + classId: classConfig?.id, + source: e.modelRun ? 'MODEL' : 'ARTIFICIAL', + sourceId: e.sourceId, + sourceType: e.sourceType, + classAttributes: objectV2, + }); + }); + + dataMeta.classifications.forEach((classification) => { + let values = utils.classificationToSave(classification); + dataAnnotations.push({ + classificationId: classification.id, + classificationAttributes: { + id: classification.id, + values: values, + }, + }); + }); + + dataInfos.push({ + dataId: dataMeta.id, + objects: infos, + dataAnnotations: dataAnnotations, + }); + }); + + let objectInfo = { + datasetId: bsState.datasetId, + dataInfos: dataInfos, + }; + bsState.saving = true; + try { + // debugger + await api.saveObject(objectInfo).then((keyMap) => { + this.updateBackId(keyMap); + }); + frames.forEach((e) => { + e.needSave = false; + }); + this.showMsg('success', this.lang('save-ok')); + } catch (e: any) { + console.error(e); + this.showMsg('error', this.lang('save-error')); + } + bsState.saving = false; + } + + updateBackId(keyMap: Record>) { + Object.keys(keyMap).forEach((dataId) => { + let dataKeyMap = keyMap[dataId]; + let annotates = this.dataManager.getFrameObject(dataId) || []; + annotates.forEach((annotate: any) => { + let frontId = annotate.uuid; + let backId = dataKeyMap[frontId]; + if (!backId) return; + annotate.userData.backId = backId; + // annotate.uuid = backId; + }); + }); + } + async getResultSources(frame?: IFrame) { + let { state } = this; + frame = frame || this.getCurrentFrame(); + if (!frame.sources) { + let sources = await api.getResultSources(frame.id); + sources.unshift({ + name: 'Without Task', + sourceId: state.config.withoutTaskId, + sourceType: SourceType.DATA_FLOW, + }); + frame.sources = sources; + } + this.setSources(frame.sources); + + // let sourceMap = {}; + // sources.forEach((e) => { + // sourceMap[e.sourceId] = true; + // }); + // state.sourceFilters = [state.config.withoutTaskId]; + // state.sources = sources; + } +} diff --git a/frontend/text-tool/src/components/Collapse/index.vue b/frontend/text-tool/src/components/Collapse/index.vue new file mode 100644 index 00000000..b640d5c5 --- /dev/null +++ b/frontend/text-tool/src/components/Collapse/index.vue @@ -0,0 +1,70 @@ + + + + + diff --git a/frontend/text-tool/src/components/Common/lang/en.ts b/frontend/text-tool/src/components/Common/lang/en.ts new file mode 100644 index 00000000..d08731bd --- /dev/null +++ b/frontend/text-tool/src/components/Common/lang/en.ts @@ -0,0 +1,8 @@ +const en = { + // general + 'class-recent': 'Recent Classes', +}; + +export type ILocale = typeof en; + +export { en }; diff --git a/frontend/text-tool/src/components/Common/lang/index.ts b/frontend/text-tool/src/components/Common/lang/index.ts new file mode 100644 index 00000000..74ea3d05 --- /dev/null +++ b/frontend/text-tool/src/components/Common/lang/index.ts @@ -0,0 +1,2 @@ +export * from './zh'; +export * from './en'; diff --git a/frontend/text-tool/src/components/Common/lang/type.ts b/frontend/text-tool/src/components/Common/lang/type.ts new file mode 100644 index 00000000..af324dfb --- /dev/null +++ b/frontend/text-tool/src/components/Common/lang/type.ts @@ -0,0 +1 @@ +export type { ILocale } from './en'; diff --git a/frontend/text-tool/src/components/Common/lang/zh.ts b/frontend/text-tool/src/components/Common/lang/zh.ts new file mode 100644 index 00000000..b3bc4685 --- /dev/null +++ b/frontend/text-tool/src/components/Common/lang/zh.ts @@ -0,0 +1,7 @@ +import { ILocale } from './type'; + +const zh: ILocale = { + 'class-recent': '最近使用标签', +}; + +export { zh }; diff --git a/frontend/text-tool/src/components/Common/selectClass.vue b/frontend/text-tool/src/components/Common/selectClass.vue new file mode 100644 index 00000000..1f420d49 --- /dev/null +++ b/frontend/text-tool/src/components/Common/selectClass.vue @@ -0,0 +1,135 @@ + + + diff --git a/frontend/text-tool/src/components/EditClass/AttrValue.vue b/frontend/text-tool/src/components/EditClass/AttrValue.vue new file mode 100644 index 00000000..68118c03 --- /dev/null +++ b/frontend/text-tool/src/components/EditClass/AttrValue.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/frontend/text-tool/src/components/EditClass/Instance.vue b/frontend/text-tool/src/components/EditClass/Instance.vue new file mode 100644 index 00000000..c7026db3 --- /dev/null +++ b/frontend/text-tool/src/components/EditClass/Instance.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/frontend/text-tool/src/components/EditClass/ModelInfo.vue b/frontend/text-tool/src/components/EditClass/ModelInfo.vue new file mode 100644 index 00000000..086db8a2 --- /dev/null +++ b/frontend/text-tool/src/components/EditClass/ModelInfo.vue @@ -0,0 +1,76 @@ + + + diff --git a/frontend/text-tool/src/components/EditClass/MsgBox.vue b/frontend/text-tool/src/components/EditClass/MsgBox.vue new file mode 100644 index 00000000..b7ffeef1 --- /dev/null +++ b/frontend/text-tool/src/components/EditClass/MsgBox.vue @@ -0,0 +1,35 @@ + + + diff --git a/frontend/text-tool/src/components/EditClass/MsgInfo.vue b/frontend/text-tool/src/components/EditClass/MsgInfo.vue new file mode 100644 index 00000000..ba72db64 --- /dev/null +++ b/frontend/text-tool/src/components/EditClass/MsgInfo.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/frontend/text-tool/src/components/EditClass/ObjectAttr.vue b/frontend/text-tool/src/components/EditClass/ObjectAttr.vue new file mode 100644 index 00000000..69febe52 --- /dev/null +++ b/frontend/text-tool/src/components/EditClass/ObjectAttr.vue @@ -0,0 +1,270 @@ + + + + + diff --git a/frontend/text-tool/src/components/EditClass/ObjectClass.vue b/frontend/text-tool/src/components/EditClass/ObjectClass.vue new file mode 100644 index 00000000..26ab8e95 --- /dev/null +++ b/frontend/text-tool/src/components/EditClass/ObjectClass.vue @@ -0,0 +1,237 @@ + + + + + diff --git a/frontend/text-tool/src/components/EditClass/ObjectItem.vue b/frontend/text-tool/src/components/EditClass/ObjectItem.vue new file mode 100644 index 00000000..5ef61f50 --- /dev/null +++ b/frontend/text-tool/src/components/EditClass/ObjectItem.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/frontend/text-tool/src/components/EditClass/bs/main.vue b/frontend/text-tool/src/components/EditClass/bs/main.vue new file mode 100644 index 00000000..df867de9 --- /dev/null +++ b/frontend/text-tool/src/components/EditClass/bs/main.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/frontend/text-tool/src/components/EditClass/index.vue b/frontend/text-tool/src/components/EditClass/index.vue new file mode 100644 index 00000000..081d9823 --- /dev/null +++ b/frontend/text-tool/src/components/EditClass/index.vue @@ -0,0 +1,325 @@ + + + + + diff --git a/frontend/text-tool/src/components/EditClass/lang/en.ts b/frontend/text-tool/src/components/EditClass/lang/en.ts new file mode 100644 index 00000000..fbe6f406 --- /dev/null +++ b/frontend/text-tool/src/components/EditClass/lang/en.ts @@ -0,0 +1,110 @@ +const en = { + // general + 'track-title': 'Tracking Object', + 'frame-object': 'Frame {{n}} Object', + 'mark-all-true': 'Mark All as True Value', + 'track-id': 'Tracking ID', + 'model-info': 'Model Info', + 'predict-class': 'Predict Class', + 'mark-present': 'Mark as Present', + 'mark-gone': 'Mark as Gone', + 'status-title': 'Status', + 'instances-title': 'Instances', + 'advanced-title': 'Advanced', + 'copy-title': 'Copy', + 'copy-success': 'Copied', + 'no-data': 'No Data', + // class + 'class-title': 'Class', + 'class-tip-info': + 'Labeling this tracking object as “{{type}}” will clear its attributes in all frames. Are you sure to change?', + 'class-tip-info-standard': + 'Labeling this tracking object as “{{type}}” will rewrite all of its L, W, H. Are you sure to change?', + 'class-length': 'Length', + 'class-width': 'Width', + 'class-height': 'Height', + 'class-points': 'Points', + 'class-recent': 'Recent Classes', + 'class-other': 'Other', + + // type + 'mark-as-standard': 'Set as Standard', + 'mark-standard': 'Standard', + 'mark-no-standard': 'Cancel', + 'object-type': 'Object Type', + 'type-tip-info': + 'Labeling this tracking object as {{type}} will rewrite all of its L, W, H. Are you sure to change?', + // instance + 'rect-title': 'Rect', + 'box-title': 'Box', + 'cloud-object': 'Cloud Point Object', + 'image-object': 'Image {{index}} Object({{type}})', + // attributes + 'attributes-title': 'Attributes', + 'attr-copy-from': 'Copy From', + 'attr-copy-to': 'Copy To', + 'attr-from-title': 'Copy attributes from other objects in this frame', + 'attr-from-object': 'Tracking Object', + 'attr-to-title': 'Copy these attributes to other frames of this tracking object', + 'attr-to-tip': '(This will overwrite original attributes)', + 'attr-to-from': 'Frame From({{ n }})', + 'attr-to-to': 'To({{ n }})', + 'attr-from-self': 'Cannot copy from self', + 'attr-from-different-class': 'Cannot copy from different classes', + // model + 'model-instance': 'Instances', + 'model-confidence': 'Confidence', + + // Merge + 'merge-title': 'Merge', + 'merge-to': 'Merge To', + 'merge-from': 'Merge From', + 'marge-target': 'Target', + // Split + 'split-title': 'Split', + 'split-btn-title': 'Split from Current Frame', + 'split-new-object': 'New Tracking Object', + 'split-new-class': 'Class', + // Delete + 'delete-title': 'Delete', + 'delete-all': 'All Frames', + 'delete-some': 'Some Frames', + 'delete-no-true': 'All Non-True Values', + 'delete-all-tip': 'You are going to delete this tracking object in all frames.', + 'delete-some-tip': 'You are going to delete this tracking object in the following frames.', + 'delete-no-true-tip': 'You are going to delete all non-True Values of this tracking object.', + 'delete-from': 'From({{ n }})', + 'delete-to': 'To({{ n }})', + + // btn + 'btn-title-cancel': 'Cancel', + 'btn-title-copy': 'Copy', + 'btn-title-merge': 'Merge', + 'btn-title-split': 'Split', + 'btn-title-delete': 'Delete', + 'btn-title-confirm': 'Confirm', + + // msg + 'msg-delete-title': 'Delete', + 'msg-delete-subtitle': 'Delete Objects?', + 'msg-merge-different-class': 'Cannot merge objects with different classes.', + 'msg-merge-conflict': 'Conflicts detected. Please check in the timeline.', + 'msg-merge-success': 'Merge success', + 'msg-split-empty': 'Cannot create empty object', + 'msg-split-success': 'Split success', + 'msg-delete-success': 'Deleted', + 'msg-copy-success': 'Copied', + 'msg-no-object': 'No Object', + + // const + Dynamic: 'Dynamic', + Fixed: 'Fixed', + Standard: 'Standard', + Copied: 'Copied', + Predicted: 'Predicted', + 'True-Value': 'True Value', +}; + +export type ILocale = typeof en; + +export { en }; diff --git a/frontend/text-tool/src/components/EditClass/lang/index.ts b/frontend/text-tool/src/components/EditClass/lang/index.ts new file mode 100644 index 00000000..74ea3d05 --- /dev/null +++ b/frontend/text-tool/src/components/EditClass/lang/index.ts @@ -0,0 +1,2 @@ +export * from './zh'; +export * from './en'; diff --git a/frontend/text-tool/src/components/EditClass/lang/type.ts b/frontend/text-tool/src/components/EditClass/lang/type.ts new file mode 100644 index 00000000..af324dfb --- /dev/null +++ b/frontend/text-tool/src/components/EditClass/lang/type.ts @@ -0,0 +1 @@ +export type { ILocale } from './en'; diff --git a/frontend/text-tool/src/components/EditClass/lang/zh.ts b/frontend/text-tool/src/components/EditClass/lang/zh.ts new file mode 100644 index 00000000..5ed60657 --- /dev/null +++ b/frontend/text-tool/src/components/EditClass/lang/zh.ts @@ -0,0 +1,107 @@ +import { ILocale } from './type'; + +const zh: ILocale = { + 'track-title': '追踪对象', + 'frame-object': '第{{n}}帧对象', + 'mark-all-true': '全标记为人工标注', + 'track-id': '追踪ID', + 'model-info': '模型信息', + 'predict-class': '模型标签', + 'mark-present': '标记为可见', + 'mark-gone': '标记为消失', + 'status-title': '状态', + 'instances-title': '实例', + 'advanced-title': '高级操作', + 'copy-title': '复制', + 'copy-success': '复制成功', + 'no-data': '无数据', + // class + 'class-title': '标签', + 'class-tip-info': + '设置该追踪对象为{{type}},将清空该追踪对象在全部帧中已标注的属性信息,是否确认?', + 'class-tip-info-standard': + '设置该追踪对象为“{{type}}”,将重写其全部结果的长宽高信息,需要继续么?', + 'class-length': '长', + 'class-width': '宽', + 'class-height': '高', + 'class-points': '点数', + 'class-recent': '最近使用标签', + 'class-other': '其他', + // type + 'mark-as-standard': '设为标准框', + 'mark-standard': '标准框', + 'mark-no-standard': '取消标准框', + 'object-type': '框类型', + 'type-tip-info': '设置该追踪对象为{{type}},将重写其全部结果的长宽高信息,需要继续么?', + // instance + 'rect-title': '4个点', + 'box-title': '8个点', + 'cloud-object': '点云对象', + 'image-object': '图片 {{index}} 对象({{type}})', + // attributes + 'attributes-title': '属性', + 'attr-copy-from': '复制从', + 'attr-copy-to': '复制去', + 'attr-from-title': '从当前帧中其他对象复制属性', + 'attr-from-object': '追踪对象', + 'attr-to-title': '将当前属性复制到该追踪对象其他帧', + 'attr-to-tip': '(此操作会覆盖原有的属性结果)', + 'attr-to-from': '从{{ n }}帧', + 'attr-to-to': '到{{ n }}帧', + 'attr-from-self': '不能复制自己的属性', + 'attr-from-different-class': '不同标签的属性不能复制', + + // model + 'model-instance': '实例', + 'model-confidence': '置信度', + + // Merge + 'merge-title': '合并', + 'merge-to': '合并到', + 'merge-from': '合并从', + 'marge-target': '目标追踪对象', + // Split + 'split-title': '拆分', + 'split-btn-title': '从当前帧拆分', + 'split-new-object': '新拆分的对象为', + 'split-new-class': '标签', + // Delete + 'delete-title': '删除', + 'delete-all': '所有帧', + 'delete-some': '部分帧', + 'delete-no-true': '非人工标注', + 'delete-all-tip': '是否要删除全部帧中的对象?', + 'delete-some-tip': '是否要删除所选帧中的对象?', + 'delete-no-true-tip': '是否删除此追踪对象下所有非人工标注对象?', + 'delete-from': '从{{ n }}帧', + 'delete-to': '到{{ n }}帧', + + // btn + 'btn-title-cancel': '取消', + 'btn-title-copy': '复制', + 'btn-title-merge': '合并', + 'btn-title-split': '拆分', + 'btn-title-delete': '删除', + 'btn-title-confirm': '确认', + // msg + 'msg-delete-title': '删除', + 'msg-delete-subtitle': '删除结果?', + 'msg-merge-different-class': '不能合并不同标签', + 'msg-merge-conflict': '存在合并冲突,请在时间轴里查看', + 'msg-merge-success': '合并成功', + 'msg-split-empty': '不能创建空对象', + 'msg-split-success': '拆分成功', + 'msg-delete-success': '删除成功', + 'msg-copy-success': '复制成功', + 'msg-no-object': '无对象', + + // const + Dynamic: '动态框', + Fixed: '固定框', + Standard: '标准框', + Copied: '复制', + Predicted: '模型预测', + 'True-Value': '人工标注', +}; + +export { zh }; diff --git a/frontend/text-tool/src/components/EditClass/type.ts b/frontend/text-tool/src/components/EditClass/type.ts new file mode 100644 index 00000000..b8cb0418 --- /dev/null +++ b/frontend/text-tool/src/components/EditClass/type.ts @@ -0,0 +1,63 @@ +import { IAttr, ResultType, ResultStatus, IClassType } from 'pc-editor'; +import { AnnotateType } from 'pc-render'; + +export interface IInstanceItem { + id: string; + name: string; + confidence: number; + info?: string; +} + +export interface IAttrItem extends IAttr { + value: any; +} + +export type MsgType = + | 'type' + | 'class' + | 'class-standard' + | 'attr-from' + | 'attr-to' + | 'merge-to' + | 'merge-from' + | 'split' + | 'delete-all' + | 'delete-range' + | 'delete-no-true'; + +export interface IState { + activeTab: string[]; + showType: 'select' | 'msg'; + // model + batchVisible: boolean; + isBatch: boolean; + batchTrackIds: string[]; + instances: IInstanceItem[]; + filterInstances: IInstanceItem[]; + modelClass: string; + confidenceRange: number[]; + // + isClassStandard: boolean; + isInvisible: boolean; + classType: string; + objectId: string; + trackId: string; + trackName: string; + trackVisible: boolean; + isStandard: boolean; + resultType: ResultType | ''; + resultStatus: ResultStatus | ''; + resultInstances: IInstanceItem[]; + annotateType: AnnotateType | ''; + attrs: IAttr[]; + // msgInfo + showMsgType: MsgType | ''; + // + // [k: string]: any; +} + +export interface IControl { + needUpdate: () => boolean; + close: () => void; + open: () => void; +} diff --git a/frontend/text-tool/src/components/EditClass/useControl.ts b/frontend/text-tool/src/components/EditClass/useControl.ts new file mode 100644 index 00000000..942b1d3a --- /dev/null +++ b/frontend/text-tool/src/components/EditClass/useControl.ts @@ -0,0 +1,18 @@ +import { inject, provide } from 'vue'; +import { IControl } from './type'; + +export const controlContext = Symbol('edit-class-control'); + +export function useProvideControl(control: IControl) { + provide(controlContext, control); +} + +export default function useInjectControl() { + let defaultControl: IControl = { + needUpdate: () => true, + close: () => {}, + open: () => {}, + }; + let control = inject(controlContext) as IControl; + return control || defaultControl; +} diff --git a/frontend/text-tool/src/components/EditClass/useEditClass.ts b/frontend/text-tool/src/components/EditClass/useEditClass.ts new file mode 100644 index 00000000..7f30959c --- /dev/null +++ b/frontend/text-tool/src/components/EditClass/useEditClass.ts @@ -0,0 +1,489 @@ +import { reactive, onMounted, onBeforeUnmount, watch } from 'vue'; +import { useClipboard } from '@vueuse/core'; +import { AttrType, IClassType, Event, utils, IUserData, Const } from 'pc-editor'; +import { AnnotateObject, Box, Rect } from 'pc-render'; +import { useInjectState } from '../../state'; +import { IState, IInstanceItem, MsgType, IControl } from './type'; +import { useInjectEditor } from '../../state'; +import * as _ from 'lodash'; +import * as THREE from 'three'; +import * as locale from './lang'; +import useControl from './useControl'; + +let SOURCE_CLASS = 'edit_class'; +// type IEmit = (event: 'close', ...args: any[]) => void; + +export default function useEditClass() { + const { copy } = useClipboard(); + let editor = useInjectEditor(); + let editorState = useInjectState(); + let control = useControl(); + // object + let trackAttrs = {} as Record; + let trackObject = {} as AnnotateObject; + let tempObjects = [] as AnnotateObject[]; + // lang + let $$ = editor.bindLocale(locale); + + let state = reactive({ + activeTab: ['attribute', 'objects', 'cuboid'], + showType: 'select', + // batch + batchVisible: true, + isBatch: false, + batchTrackIds: [], + instances: [], + filterInstances: [], + modelClass: '', + confidenceRange: [0.2, 1], + // + objectId: '', + trackId: '', + trackName: '', + trackVisible: false, + isStandard: false, + resultStatus: '', + resultType: '', + resultInstances: [], + annotateType: '', + classType: '', + isClassStandard: false, + isInvisible: false, + attrs: [], + // msg + showMsgType: '', + // + }); + watch( + () => [state.confidenceRange, state.instances], + () => { + let [min, max] = state.confidenceRange; + let filterInstances = state.instances.filter((e) => { + let confidence = e.confidence || 0; + return confidence >= min && confidence <= max; + }); + let objectMap = {} as Record; + tempObjects.forEach((e) => { + objectMap[e.uuid] = e; + }); + let noVisible = filterInstances.filter((e) => !objectMap[e.id].visible); + state.filterInstances = filterInstances; + state.batchVisible = noVisible.length === 0; + }, + ); + + let update = _.debounce(() => { + if (!control.needUpdate()) return; + clear(); + console.log('class edit update'); + if (state.isBatch) { + showBatchObject(state.batchTrackIds); + } else { + showObject(state.trackId); + } + }, 100); + + onMounted(() => { + editor.addEventListener(Event.SHOW_CLASS_INFO, (data: any) => { + let trackIds = data.data.id; + state.showType = 'msg'; + handleObject(trackIds); + }); + editor.addEventListener(Event.ANNOTATE_SELECT, onSelect); + editor.addEventListener(Event.ANNOTATE_REMOVE, syncUpdate); + editor.addEventListener(Event.ANNOTATE_ADD, syncUpdate); + editor.addEventListener(Event.ANNOTATE_CHANGE, syncUpdate); + }); + + onBeforeUnmount(() => { + editor.removeEventListener(Event.ANNOTATE_SELECT, onSelect); + editor.removeEventListener(Event.ANNOTATE_REMOVE, syncUpdate); + editor.removeEventListener(Event.ANNOTATE_ADD, syncUpdate); + editor.removeEventListener(Event.ANNOTATE_CHANGE, syncUpdate); + }); + + function onClearMergeSplit() { + if ( + state.showMsgType === 'split' || + state.showMsgType === 'merge-from' || + state.showMsgType === 'merge-to' + ) { + state.showMsgType = ''; + } + } + + function onSelect(data: any) { + let selection = data.data.curSelection as AnnotateObject[]; + if (selection.length > 0) { + state.showType = 'select'; + handleObject(selection[0].userData.trackId); + } else { + if (state.showType === 'select') close(); + } + } + + function syncUpdate() { + if (editor.eventSource === SOURCE_CLASS) return; + update(); + } + + function handleObject(trackId: string | string[]) { + if (Array.isArray(trackId)) { + state.batchTrackIds = trackId; + state.isBatch = true; + } else { + state.trackId = trackId; + state.isBatch = false; + } + update(); + } + + function clear() { + state.batchVisible = true; + state.classType = ''; + state.isClassStandard = false; + state.isStandard = false; + state.isInvisible = false; + state.resultType = ''; + state.resultStatus = ''; + state.resultInstances = []; + state.objectId = ''; + state.trackName = ''; + state.trackVisible = false; + state.annotateType = ''; + + state.modelClass = ''; + state.instances = []; + state.attrs = []; + state.showMsgType = ''; + + // state.trackId = ''; + // state.isBatch = false; + } + + function close() { + // emit('close'); + control.close(); + } + + function showBatchObject(trackIds: string[]) { + let trackIdMap = {}; + trackIds.forEach((id) => (trackIdMap[id] = true)); + let objects = editor.pc + .getAnnotate3D() + .filter((e) => trackIdMap[e.userData.trackId]) as AnnotateObject[]; + + if (objects.length === 0) { + close(); + return; + } + + let object = objects[0]; + state.objectId = new Date().getTime() + ''; + state.modelClass = (object.userData as IUserData).modelClass || ''; + state.classType = object.userData.classId || object.userData.classType || ''; + + let confidenceMax = 0; + let confidenceMin = 1; + let instances: IInstanceItem[] = objects.map((e) => { + let name = e.userData.trackName || ''; + let confidence = e.userData.confidence || 1; + if (confidence > confidenceMax) confidenceMax = confidence; + if (confidence < confidenceMin) confidenceMin = confidence; + return { id: e.uuid, name: name, confidence: confidence }; + }); + + let confidenceMinFix = +confidenceMin.toFixed(2); + confidenceMinFix = Math.max(confidenceMinFix - 0.01, 0); + let confidenceMaxFix = +confidenceMax.toFixed(2); + confidenceMaxFix = Math.min(confidenceMaxFix + 0.01, 1); + + state.instances = instances; + state.confidenceRange = [confidenceMinFix, confidenceMaxFix]; + + tempObjects = objects; + } + + function showObject(trackId: string) { + let annotate2d = editor.pc.getAnnotate2D(); + let annotate3d = editor.pc.getAnnotate3D(); + + let info = getAnnotateByTrackId([...annotate3d, ...annotate2d], trackId); + + if (info.annotate3D.length === 0 && info.annotate2D.length === 0) { + close(); + return; + } + + let object = info.annotate3D.length > 0 ? info.annotate3D[0] : info.annotate2D[0]; + let userData = editor.getObjectUserData(object); + + state.objectId = object.uuid; + state.modelClass = userData.modelClass || ''; + state.classType = userData.classId || userData.classType || ''; + // state.isInvisible = !!userData.invisibleFlag; + state.trackId = userData.trackId || ''; + state.trackName = userData.trackName || ''; + // state.isStandard = userData.isStandard || false; + // state.resultStatus = userData.resultStatus || Const.True_Value; + // state.resultType = userData.resultType || Const.Dynamic; + + // temp + trackObject = object; + tempObjects = [...info.annotate3D, ...info.annotate2D]; + + let trackVisible = false; + let rectTitle = $$('rect-title'); + let boxTitle = $$('box-title'); + state.resultInstances = tempObjects.map((e) => { + let userData = e.userData as Required; + let is3D = e instanceof Box; + let info = $$('cloud-object'); + if (!is3D) { + let isRect = e instanceof Rect; + let index = get2DIndex((e as Rect).viewId); + info = $$('image-object', { + index: index + 1, + type: isRect ? rectTitle : boxTitle, + }); + // info = `Image ${index + 1} Object(${isRect ? 'Rect' : 'Box'})`; + } + + if (e.visible) trackVisible = true; + + return { id: e.uuid, name: userData.id.slice(-4), info, confidence: 0 }; + }); + + state.trackVisible = trackVisible; + // state.annotateType = object.annotateType; + if (state.classType) { + updateAttrInfo(userData, state.classType); + updateClassInfo(); + } + } + + function updateAttrInfo(userData: IUserData, classType: string) { + let classConfig = editor.getClassType(classType); + if (!classConfig) return; + let attrs = userData.attrs || {}; + // let newAttrs = classConfig.attrs.map((e) => { + // let defaultValue = e.type === AttrType.MULTI_SELECTION ? [] : ''; + // // The array type may be a single value + // if (e.type === AttrType.MULTI_SELECTION && attrs[e.id] && !Array.isArray(attrs[e.id])) { + // attrs[e.id] = [attrs[e.id]]; + // } + // let value = e.id in attrs ? attrs[e.id] : defaultValue; + // return { ...e, value }; + // }); + // state.attrs = newAttrs; + state.attrs = utils.copyClassAttrs(classConfig, attrs); + trackAttrs = JSON.parse(JSON.stringify(attrs)); + } + + function onInstanceRemove(item: IInstanceItem) { + state.instances = state.instances.filter((e) => e.id !== item.id); + tempObjects = tempObjects.filter((e) => e.uuid !== item.id); + } + + function onToggleObjectsVisible() { + let visible = !state.batchVisible; + state.batchVisible = visible; + + let objects = getFilterObjects(); + if (objects.length > 0) { + // pc.setVisible(objects, visible); + editor.cmdManager.execute('toggle-visible', { objects: objects, visible }); + } + } + + function getFilterObjects() { + let insMap = {}; + state.filterInstances.forEach((e) => (insMap[e.id] = true)); + let objects = tempObjects.filter((e) => insMap[e.uuid]); + return objects; + } + + function onRemoveObjects() { + if (tempObjects.length === 0) return; + editor + .showConfirm({ title: $$('msg-delete-title'), subTitle: $$('msg-delete-subtitle') }) + .then( + () => { + let objects = getFilterObjects(); + editor.cmdManager.execute('delete-object', [{ objects: objects }]); + + let [min, max] = state.confidenceRange; + state.instances = state.instances.filter( + (e) => !(e.confidence >= min && e.confidence <= max), + ); + if (state.instances.length === 0) close(); + }, + () => {}, + ); + } + + function updateClassInfo() { + let classConfig = editor.getClassType(state.classType); + if (!classConfig) return; + + state.isClassStandard = classConfig.type === 'standard'; + } + + function onClassChange() { + if (state.isBatch) { + updateClassMulti(); + return; + } + + updateClassInfo(); + + // let classConfig = editor.getClassType(state.classType); + // let size3D = undefined; + let classConfig = editor.getClassType(state.classType); + let userData = { + classType: classConfig?.name, + classId: classConfig?.id, + attrs: {}, + resultStatus: Const.True_Value, + } as IUserData; + + editor.cmdManager.execute('update-object-user-data', { + objects: tempObjects, + + data: userData, + }); + + state.resultStatus = Const.True_Value; + updateAttrInfo(trackObject.userData, state.classType); + } + + function updateClassMulti() { + let { frameIndex, frames } = editor.state; + + let objects = getFilterObjects(); + let trackIdMap = {}; + objects.forEach((e) => (trackIdMap[e.userData.trackId] = true)); + let ids = Object.keys(trackIdMap); + if (ids.length === 0) return; + + // let userData = {} as IUserData; + // userData.classType = state.classType; + // userData.attrs = {}; + // userData.resultStatus = Const.True_Value; + let classConfig = editor.getClassType(state.classType); + editor.cmdManager.execute('update-object-user-data', { + objects: tempObjects, + + data: { + classType: classConfig?.name, + classId: classConfig?.id, + }, + }); + } + + // attr + let updateTrackAttr = _.debounce(() => { + editor.withEventSource(SOURCE_CLASS, () => { + let attrs = JSON.parse(JSON.stringify(trackAttrs)); + editor.cmdManager.execute('update-object-user-data', { + objects: tempObjects, + data: { attrs }, + }); + }); + }, 100); + + function onAttChange(name: string, value: any) { + trackAttrs[name] = value; + updateTrackAttr(); + state.resultStatus = Const.True_Value; + } + + function onObjectInstanceRemove(item: IInstanceItem) { + state.showType = 'msg'; + let annotate = tempObjects.find((e) => e.uuid === item.id); + tempObjects = tempObjects.filter((e) => e.uuid !== item.id); + if (annotate) { + editor.cmdManager.withGroup(() => { + if (tempObjects.length > 0) { + editor.cmdManager.execute('select-object', tempObjects); + } + editor.cmdManager.execute('delete-object', annotate); + }); + } + + if (tempObjects.length === 0) { + close(); + } + } + + function copyAttrFrom(trackId: string) { + // console.log(trackId); + let box = editor.pc.getAnnotate3D().find((e) => e.userData.trackId === trackId) as Box; + if (box) { + let attrs = JSON.parse(JSON.stringify(box.userData.attrs)); + editor.cmdManager.execute('update-object-user-data', { + objects: tempObjects, + data: { attrs: attrs }, + }); + trackObject.userData.attrs = attrs; + updateAttrInfo(trackObject.userData, state.classType); + editor.showMsg('success', $$('msg-copy-success')); + } else { + editor.showMsg('error', $$('msg-no-object')); + } + } + + function onToggleTrackVisible() { + let visible = !state.trackVisible; + state.trackVisible = visible; + + let objects = tempObjects; + state.showType = 'msg'; + editor.cmdManager.execute('toggle-visible', { objects, visible }); + } + + function onCopy() { + copy(state.trackId); + editor.showMsg('success', $$('copy-success')); + } + + return { + state, + update, + control, + onAttChange, + onClassChange, + onInstanceRemove, + onToggleObjectsVisible, + onRemoveObjects, + // onObjectStatusChange, + onObjectInstanceRemove, + copyAttrFrom, + // copyAttrTo, + onToggleTrackVisible, + // toggleStandard, + }; +} + +function getAnnotateByTrackId(annotates: AnnotateObject[], trackId: string) { + let annotate3D = [] as AnnotateObject[]; + let annotate2D = [] as AnnotateObject[]; + annotates.forEach((obj) => { + let userData = obj.userData as Required; + if (userData.trackId !== trackId) return; + + if (obj instanceof Box) { + annotate3D.push(obj); + } else { + annotate2D.push(obj); + } + }); + + return { annotate2D, annotate3D }; +} + +function get2DIndex(viewId: string) { + return parseInt((viewId.match(/[0-9]{1,5}$/) as any)[0]); +} + +function getControl() {} diff --git a/frontend/text-tool/src/components/Editor/index.vue b/frontend/text-tool/src/components/Editor/index.vue new file mode 100644 index 00000000..05fa51db --- /dev/null +++ b/frontend/text-tool/src/components/Editor/index.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/frontend/text-tool/src/components/Editor/main.vue b/frontend/text-tool/src/components/Editor/main.vue new file mode 100644 index 00000000..74748a71 --- /dev/null +++ b/frontend/text-tool/src/components/Editor/main.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/frontend/text-tool/src/components/Editor/text-main.vue b/frontend/text-tool/src/components/Editor/text-main.vue new file mode 100644 index 00000000..628096a6 --- /dev/null +++ b/frontend/text-tool/src/components/Editor/text-main.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/frontend/text-tool/src/components/Header/index.vue b/frontend/text-tool/src/components/Header/index.vue new file mode 100644 index 00000000..c3b12ee9 --- /dev/null +++ b/frontend/text-tool/src/components/Header/index.vue @@ -0,0 +1,324 @@ + + + + + diff --git a/frontend/text-tool/src/components/Header/lang/en.ts b/frontend/text-tool/src/components/Header/lang/en.ts new file mode 100644 index 00000000..39d6dfa3 --- /dev/null +++ b/frontend/text-tool/src/components/Header/lang/en.ts @@ -0,0 +1,16 @@ +const en = { + 'btn-close': 'Close', + 'btn-save': 'Save', + 'btn-shortcut': 'Shortcut', + 'btn-valid': 'Mark as Valid', + 'btn-invalid': 'Mark as Invalid', + 'btn-skip': 'Skip', + 'btn-skipped': 'Skipped', + 'btn-submit': 'Submit', + 'btn-update': 'Update', + 'btn-modify': 'Modify', + 'btn-full': 'Full Screen', + 'btn-full-exit': 'Exit Full Screen', +}; +export type ILocale = typeof en; +export { en }; diff --git a/frontend/text-tool/src/components/Header/lang/index.ts b/frontend/text-tool/src/components/Header/lang/index.ts new file mode 100644 index 00000000..0db7f505 --- /dev/null +++ b/frontend/text-tool/src/components/Header/lang/index.ts @@ -0,0 +1,2 @@ +export * from './en'; +export * from './zh'; diff --git a/frontend/text-tool/src/components/Header/lang/type.ts b/frontend/text-tool/src/components/Header/lang/type.ts new file mode 100644 index 00000000..af324dfb --- /dev/null +++ b/frontend/text-tool/src/components/Header/lang/type.ts @@ -0,0 +1 @@ +export type { ILocale } from './en'; diff --git a/frontend/text-tool/src/components/Header/lang/zh.ts b/frontend/text-tool/src/components/Header/lang/zh.ts new file mode 100644 index 00000000..f4c1c11c --- /dev/null +++ b/frontend/text-tool/src/components/Header/lang/zh.ts @@ -0,0 +1,16 @@ +import { ILocale } from './type'; +const zh: ILocale = { + 'btn-close': '关闭', + 'btn-save': '保存', + 'btn-shortcut': '快捷键', + 'btn-valid': '标记为有效帧', + 'btn-invalid': '标记为无效帧', + 'btn-skip': '跳过', + 'btn-skipped': '已跳过', + 'btn-submit': '提交', + 'btn-update': '更新', + 'btn-modify': '编辑', + 'btn-full': '全屏', + 'btn-full-exit': '退出全屏', +}; +export { zh }; diff --git a/frontend/text-tool/src/components/Header/useHeader.ts b/frontend/text-tool/src/components/Header/useHeader.ts new file mode 100644 index 00000000..9c28a470 --- /dev/null +++ b/frontend/text-tool/src/components/Header/useHeader.ts @@ -0,0 +1,302 @@ +import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'; +import { useInjectEditor } from '../../state'; +import { Event, IFrame, StatusType } from 'pc-editor'; +import * as _ from 'lodash'; +import * as api from '../../api'; +import * as locale from './lang'; +import screenFull from 'screenfull'; +import { Modal } from 'ant-design-vue'; +// import { SideRenderView } from 'pc-render'; + +export default function useHeader() { + let editor = useInjectEditor(); + let $$ = editor.bindLocale(locale); + let { state, bsState } = editor; + let editorState = editor.state; + let dataIndex = ref(state.frameIndex + 1); + let iState = reactive({ + fullScreen: false, + dataName: '', + }); + watch( + () => state.frameIndex, + () => { + if (dataIndex.value !== state.frameIndex + 1) dataIndex.value = state.frameIndex + 1; + updateName(); + }, + ); + + onMounted(() => { + editor.addEventListener(Event.RESOURCE_LOAD_COMPLETE, updateName); + }); + + let currentFrame = computed(() => { + let { frameIndex, frames } = editor.state; + return frames[frameIndex]; + }); + + let onIndexChange = _.debounce(() => { + console.log('change', dataIndex.value); + if (dataIndex.value && dataIndex.value - 1 >= 0) editor.loadFrame(dataIndex.value - 1); + }, 200); + + function onIndexBlur() { + if (!dataIndex.value) dataIndex.value = state.frameIndex + 1; + } + async function onFullScreen() { + if (iState.fullScreen) { + await screenFull.exit(); + } else { + await screenFull.request(); + } + iState.fullScreen = !iState.fullScreen; + // setTimeout(() => { + // editor.pc.renderViews.forEach((view) => { + // if (view instanceof SideRenderView) view.fitObject(); + // view.render(); + // }); + // }, 400); + } + + let updateName = () => { + const { id } = currentFrame.value; + iState.dataName = editor.dataResource.dataMap[id]?.name || ''; + }; + function onSave() { + editor.saveObject(); + } + + function onPre() { + editor.loadFrame(state.frameIndex - 1); + } + function onNext() { + editor.loadFrame(state.frameIndex + 1); + } + + async function onClose() { + let status = ''; + if (editor.needSave()) { + status = await editor + .showModal('ModalConfirm', { + title: '', + closable: false, + data: { + // btns: ['ok'], + okText: 'Save', + content: 'Save Change', + subContent: 'Do you want to save changes?', + }, + }) + .then( + async (_status: 'discard' | 'ok') => { + return _status; + }, + async (error) => 'cancel', + ); + } + + if (status === 'ok') { + await editor.saveObject(); + } else if (status === 'discard') { + // clear save status + editor.state.frames.forEach((e) => { + e.needSave = false; + }); + } else if (status === 'cancel') { + return; + } + + await unlockData(); + } + + async function unlockData() { + if (editor.state.modeConfig.name !== 'view') { + await api.unlockRecord(editor.bsState.recordId); + } + closeTab(); + } + + function closeTab() { + let win = window.open('about:blank', '_self'); + win && win.close(); + } + + let blocking = computed(() => { + return ( + bsState.saving || + bsState.validing || + bsState.submitting || + bsState.modifying || + editorState.status === StatusType.Loading || + editorState.status === StatusType.Create || + editorState.status === StatusType.Play + ); + }); + + function onHelp() { + editor.showModal('ModelHelp', { title: 'Help', width: 1000 }).catch(() => {}); + } + + async function onToggleValid() { + let { frameIndex, frames } = editor.state; + let frame = frames[frameIndex]; + + bsState.validing = true; + try { + if (frame.dataStatus === 'INVALID') { + await api.validData(frame.id); + frame.dataStatus = 'VALID'; + } else { + await api.invalidData(frame.id); + frame.dataStatus = 'INVALID'; + } + } catch (error: any) { + editor.handleErr(error, 'Operation Error'); + } + bsState.validing = false; + } + + async function onToggleSkip() { + let { frameIndex, frames } = editor.state; + // frame.skipped = !frame.skipped; + await editor.saveObject([frames[frameIndex]]); + if (frameIndex < frames.length - 1) { + await editor.loadFrame(frameIndex + 1); + } else { + editor.showMsg('warning', 'This is last data'); + } + } + async function onSubmit() { + let { frameIndex, frames } = editor.state; + let frame = frames[frameIndex]; + + let objects = editor.dataManager.getFrameObject(frame.id) || []; + + let continueFlag = true; + if (frame.dataStatus === 'VALID' && objects.length === 0) { + await editor + .showConfirm({ + title: 'Tip', + subTitle: + "you don't have any annotation yet, are you sure you want to submit this data? If you can't annotate this data, you'd better mark this data as invalid. Cancel/ submit anyway", + }) + .then(async () => { + await onToggleValid(); + }) + .catch(() => { + continueFlag = false; + }); + } + + if (!continueFlag) return; + + // if (frame.skipped) return; + bsState.submitting = true; + try { + await editor.saveObject([frame], true); + await api.submitData(frame.id); + await updateDataStatus(frame); + editor.showMsg('success', 'Submit Success'); + } catch (error: any) { + editor.handleErr(error, 'Operation Error'); + } + bsState.submitting = false; + + // not last frame + if (frameIndex !== frames.length - 1) { + editor.loadFrame(frameIndex + 1); + } else { + // last frame + let next = nextNotAnnotate(); + if (next < 0) { + editor + .showConfirm({ + title: 'Well Done!', + subTitle: 'You have finish all the annotation!', + okText: 'Close and release those data', + centered: true, + }) + .then(() => { + unlockData(); + }) + .catch(() => {}); + } else { + editor.loadFrame(next); + } + } + } + + async function updateDataStatus(frame: IFrame) { + let statusMap = await api.getDataStatus([frame.id]); + + if (statusMap[frame.id]) { + let status = statusMap[frame.id]; + frame.dataStatus = status.status || 'VALID'; + frame.annotationStatus = status.annotationStatus || 'NOT_ANNOTATED'; + } + } + + function nextNotAnnotate() { + let { frames } = editor.state; + + let frame = frames.find((e) => { + return e.annotationStatus === 'NOT_ANNOTATED'; + }); + if (frame) return editor.getFrameIndex(frame.id); + return -1; + } + + async function onModify() { + let { bsState } = editor; + let frame = editor.getCurrentFrame(); + let config = { + dataIds: [frame.id], + dataType: 'SINGLE_DATA', + datasetId: bsState.datasetId, + }; + + bsState.modifying = true; + try { + let recordInfo = await api.getLockRecord(bsState.datasetId); + if (recordInfo.data && recordInfo.data.recordId) { + editor.showMsg('warning', 'You have 1 data occupied'); + bsState.modifying = false; + return; + } + + let data = await api.annotateData(config); + if (data.code === 'OK' && data.data) { + let recordId = data.data; + let host = location.host || location.hostname; + let pathname = location.pathname; + let protocol = location.protocol; + location.href = `${protocol}//${host + pathname}?recordId=${recordId}`; + } else { + editor.showMsg('warning', data.message || `Operation Failed`); + } + } catch (error: any) { + editor.handleErr(error, 'Operation Failed'); + } + bsState.modifying = false; + } + + return { + $$, + iState, + currentFrame, + blocking, + dataIndex, + onIndexChange, + onFullScreen, + onHelp, + onIndexBlur, + onSave, + onPre, + onNext, + onClose, + onToggleValid, + onToggleSkip, + onSubmit, + onModify, + }; +} diff --git a/frontend/text-tool/src/components/ImgView/ChangeRef.vue b/frontend/text-tool/src/components/ImgView/ChangeRef.vue new file mode 100644 index 00000000..48c004e5 --- /dev/null +++ b/frontend/text-tool/src/components/ImgView/ChangeRef.vue @@ -0,0 +1,162 @@ + + + + + diff --git a/frontend/text-tool/src/components/ImgView/Image2D.vue b/frontend/text-tool/src/components/ImgView/Image2D.vue new file mode 100644 index 00000000..915db140 --- /dev/null +++ b/frontend/text-tool/src/components/ImgView/Image2D.vue @@ -0,0 +1,180 @@ + + + + + diff --git a/frontend/text-tool/src/components/ImgView/Image2DMax.vue b/frontend/text-tool/src/components/ImgView/Image2DMax.vue new file mode 100644 index 00000000..b92708d0 --- /dev/null +++ b/frontend/text-tool/src/components/ImgView/Image2DMax.vue @@ -0,0 +1,407 @@ + + + + + diff --git a/frontend/text-tool/src/components/ImgView/index.vue b/frontend/text-tool/src/components/ImgView/index.vue new file mode 100644 index 00000000..baeceab6 --- /dev/null +++ b/frontend/text-tool/src/components/ImgView/index.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/frontend/text-tool/src/components/ImgView/lang/en.ts b/frontend/text-tool/src/components/ImgView/lang/en.ts new file mode 100644 index 00000000..d428bab1 --- /dev/null +++ b/frontend/text-tool/src/components/ImgView/lang/en.ts @@ -0,0 +1,7 @@ +const en = { + close: 'close', + 'show-camera': 'Show camera region', + 'link-to': 'Link to', +}; +export type ILocale = typeof en; +export { en }; diff --git a/frontend/text-tool/src/components/ImgView/lang/index.ts b/frontend/text-tool/src/components/ImgView/lang/index.ts new file mode 100644 index 00000000..0db7f505 --- /dev/null +++ b/frontend/text-tool/src/components/ImgView/lang/index.ts @@ -0,0 +1,2 @@ +export * from './en'; +export * from './zh'; diff --git a/frontend/text-tool/src/components/ImgView/lang/type.ts b/frontend/text-tool/src/components/ImgView/lang/type.ts new file mode 100644 index 00000000..af324dfb --- /dev/null +++ b/frontend/text-tool/src/components/ImgView/lang/type.ts @@ -0,0 +1 @@ +export type { ILocale } from './en'; diff --git a/frontend/text-tool/src/components/ImgView/lang/zh.ts b/frontend/text-tool/src/components/ImgView/lang/zh.ts new file mode 100644 index 00000000..8d57f1ba --- /dev/null +++ b/frontend/text-tool/src/components/ImgView/lang/zh.ts @@ -0,0 +1,7 @@ +import { ILocale } from './type'; +const zh: ILocale = { + close: '关闭', + 'show-camera': '显示相机区域', + 'link-to': '关联到', +}; +export { zh }; diff --git a/frontend/text-tool/src/components/ImgView/useProxy.ts b/frontend/text-tool/src/components/ImgView/useProxy.ts new file mode 100644 index 00000000..50ceaed2 --- /dev/null +++ b/frontend/text-tool/src/components/ImgView/useProxy.ts @@ -0,0 +1,12 @@ +import { inject, provide } from 'vue'; +import { Image2DRenderProxy } from 'pc-render'; + +export const controlContext = Symbol('image-2d-proxy'); + +export function useProvideProxy(proxy: Image2DRenderProxy) { + provide(controlContext, proxy); +} + +export default function useInjectProxy() { + return inject(controlContext) as Image2DRenderProxy; +} diff --git a/frontend/text-tool/src/components/Layout/index.vue b/frontend/text-tool/src/components/Layout/index.vue new file mode 100644 index 00000000..941a60e4 --- /dev/null +++ b/frontend/text-tool/src/components/Layout/index.vue @@ -0,0 +1,309 @@ + + + + + diff --git a/frontend/text-tool/src/components/MainView/Annotation.vue b/frontend/text-tool/src/components/MainView/Annotation.vue new file mode 100644 index 00000000..301f6ca7 --- /dev/null +++ b/frontend/text-tool/src/components/MainView/Annotation.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/frontend/text-tool/src/components/MainView/Info.vue b/frontend/text-tool/src/components/MainView/Info.vue new file mode 100644 index 00000000..b68ed6f1 --- /dev/null +++ b/frontend/text-tool/src/components/MainView/Info.vue @@ -0,0 +1,165 @@ + + + + + diff --git a/frontend/text-tool/src/components/MainView/Labels.vue b/frontend/text-tool/src/components/MainView/Labels.vue new file mode 100644 index 00000000..62c19e10 --- /dev/null +++ b/frontend/text-tool/src/components/MainView/Labels.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/frontend/text-tool/src/components/MainView/index.vue b/frontend/text-tool/src/components/MainView/index.vue new file mode 100644 index 00000000..33185fa7 --- /dev/null +++ b/frontend/text-tool/src/components/MainView/index.vue @@ -0,0 +1,215 @@ + + + + + diff --git a/frontend/text-tool/src/components/MainView/lang/en.ts b/frontend/text-tool/src/components/MainView/lang/en.ts new file mode 100644 index 00000000..af8834cc --- /dev/null +++ b/frontend/text-tool/src/components/MainView/lang/en.ts @@ -0,0 +1,16 @@ +const en = { + // info + 'info-name': 'Class', + 'info-empty': 'no class', + 'info-infinity': '∞', + 'info-l': 'L', + 'info-w': 'W', + 'info-h': 'H', + 'info-point': 'Point', + 'info-position': 'Position', + 'info-model': 'Model', + reset: 'Reset', + 'height-range': 'Height Range', +}; +export type ILocale = typeof en; +export { en }; diff --git a/frontend/text-tool/src/components/MainView/lang/index.ts b/frontend/text-tool/src/components/MainView/lang/index.ts new file mode 100644 index 00000000..0db7f505 --- /dev/null +++ b/frontend/text-tool/src/components/MainView/lang/index.ts @@ -0,0 +1,2 @@ +export * from './en'; +export * from './zh'; diff --git a/frontend/text-tool/src/components/MainView/lang/type.ts b/frontend/text-tool/src/components/MainView/lang/type.ts new file mode 100644 index 00000000..af324dfb --- /dev/null +++ b/frontend/text-tool/src/components/MainView/lang/type.ts @@ -0,0 +1 @@ +export type { ILocale } from './en'; diff --git a/frontend/text-tool/src/components/MainView/lang/zh.ts b/frontend/text-tool/src/components/MainView/lang/zh.ts new file mode 100644 index 00000000..bc205f54 --- /dev/null +++ b/frontend/text-tool/src/components/MainView/lang/zh.ts @@ -0,0 +1,16 @@ +import { ILocale } from './type'; +const zh: ILocale = { + // info + 'info-name': '标签', + 'info-empty': '无', + 'info-infinity': '正无穷', + 'info-l': '长度', + 'info-w': '宽度', + 'info-h': '高度', + 'info-point': '点数', + 'info-position': '位置', + 'info-model': '模型', + reset: '重置', + 'height-range': '标注高度', +}; +export { zh }; diff --git a/frontend/text-tool/src/components/MainView/setting.vue b/frontend/text-tool/src/components/MainView/setting.vue new file mode 100644 index 00000000..62281118 --- /dev/null +++ b/frontend/text-tool/src/components/MainView/setting.vue @@ -0,0 +1,114 @@ + + + diff --git a/frontend/text-tool/src/components/MainView/sub/Check.vue b/frontend/text-tool/src/components/MainView/sub/Check.vue new file mode 100644 index 00000000..eaae4707 --- /dev/null +++ b/frontend/text-tool/src/components/MainView/sub/Check.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/frontend/text-tool/src/components/MainView/sub/Radio.vue b/frontend/text-tool/src/components/MainView/sub/Radio.vue new file mode 100644 index 00000000..4194b983 --- /dev/null +++ b/frontend/text-tool/src/components/MainView/sub/Radio.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/frontend/text-tool/src/components/MainView/sub/Select.vue b/frontend/text-tool/src/components/MainView/sub/Select.vue new file mode 100644 index 00000000..29d530d4 --- /dev/null +++ b/frontend/text-tool/src/components/MainView/sub/Select.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/frontend/text-tool/src/components/MainView/sub/Text.vue b/frontend/text-tool/src/components/MainView/sub/Text.vue new file mode 100644 index 00000000..17b2c745 --- /dev/null +++ b/frontend/text-tool/src/components/MainView/sub/Text.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/frontend/text-tool/src/components/Modal/Confirm.ts b/frontend/text-tool/src/components/Modal/Confirm.ts new file mode 100644 index 00000000..c82d0da2 --- /dev/null +++ b/frontend/text-tool/src/components/Modal/Confirm.ts @@ -0,0 +1,35 @@ +import { Editor, IConfirmOption, StatusType } from 'pc-editor'; +import { Modal } from 'ant-design-vue'; +import { createVNode } from 'vue'; + +export default function injectConfirm(editor: Editor) { + editor.showConfirm = (config = {} as IConfirmOption) => { + let { title = '', subTitle = '', okText, okDanger, cancelText, centered } = config; + editor.state.status = StatusType.Confirm; + return new Promise((resolve, reject) => { + Modal.confirm({ + title: () => title, + okText, + cancelText, + centered, + okButtonProps: { + danger: okDanger, + }, + content: () => + createVNode( + 'div', + { style: 'font-size:12px;color: rgb(161 161 161);' }, + subTitle, + ), + onOk() { + editor.state.status = StatusType.Default; + resolve(); + }, + onCancel() { + editor.state.status = StatusType.Default; + reject(); + }, + }); + }); + }; +} diff --git a/frontend/text-tool/src/components/Modal/Loading.vue b/frontend/text-tool/src/components/Modal/Loading.vue new file mode 100644 index 00000000..f20904e6 --- /dev/null +++ b/frontend/text-tool/src/components/Modal/Loading.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/frontend/text-tool/src/components/Modal/Message.ts b/frontend/text-tool/src/components/Modal/Message.ts new file mode 100644 index 00000000..0c731170 --- /dev/null +++ b/frontend/text-tool/src/components/Modal/Message.ts @@ -0,0 +1,13 @@ +import { Editor, MsgType } from 'pc-editor'; +import { message } from 'ant-design-vue'; + +message.config({ + maxCount: 1, + duration: 3, +}); + +export default function injectMessage(editor: Editor) { + editor.showMsg = (type: MsgType, msg: string) => { + message[type](msg); + }; +} diff --git a/frontend/text-tool/src/components/Modal/index.vue b/frontend/text-tool/src/components/Modal/index.vue new file mode 100644 index 00000000..88e2d387 --- /dev/null +++ b/frontend/text-tool/src/components/Modal/index.vue @@ -0,0 +1,143 @@ + + + + + diff --git a/frontend/text-tool/src/components/Modal/lang/en.ts b/frontend/text-tool/src/components/Modal/lang/en.ts new file mode 100644 index 00000000..371d7ab8 --- /dev/null +++ b/frontend/text-tool/src/components/Modal/lang/en.ts @@ -0,0 +1,21 @@ +const en = { + 'title-action': 'Actions', + 'title-edit': 'Edit Cuboid', + 'title-display': 'Display', + 'hk-create': 'Create Object', + 'hk-del':'Delete Object', + 'hk-undo': 'Undo', + 'hk-redo': 'Redo', + 'hk-move-ws': 'Move cuboid up/down', + 'hk-move-ad': 'Move cuboid backward/forward', + 'hk-move-qe': 'Move cuboid left/right', + 'hk-rotate': 'Rotate cuboid left/right', + 'hk-attribute':'Show/hide classes and attributes pad', + 'hk-label': 'Show/Hide Label', + 'hk-rotate-head': 'Rotate Head', + 'hk-axis': 'Show/hide Coordinate Axis', + 'hk-filter': 'Filter other object in 2d view', + 'hk-measure': 'Show/hide Distance Measure', +}; +export type ILocale = typeof en; +export { en }; diff --git a/frontend/text-tool/src/components/Modal/lang/index.ts b/frontend/text-tool/src/components/Modal/lang/index.ts new file mode 100644 index 00000000..0db7f505 --- /dev/null +++ b/frontend/text-tool/src/components/Modal/lang/index.ts @@ -0,0 +1,2 @@ +export * from './en'; +export * from './zh'; diff --git a/frontend/text-tool/src/components/Modal/lang/type.ts b/frontend/text-tool/src/components/Modal/lang/type.ts new file mode 100644 index 00000000..af324dfb --- /dev/null +++ b/frontend/text-tool/src/components/Modal/lang/type.ts @@ -0,0 +1 @@ +export type { ILocale } from './en'; diff --git a/frontend/text-tool/src/components/Modal/lang/zh.ts b/frontend/text-tool/src/components/Modal/lang/zh.ts new file mode 100644 index 00000000..933535e1 --- /dev/null +++ b/frontend/text-tool/src/components/Modal/lang/zh.ts @@ -0,0 +1,21 @@ +import { ILocale } from './type'; +const zh: ILocale = { + 'title-action': '操作', + 'title-edit': '编辑3D框', + 'title-display': '显示', + 'hk-create': '创建对象', + 'hk-del':'删除对象', + 'hk-undo': '撤销', + 'hk-redo': '重做', + 'hk-move-ws': '上 / 下移动', + 'hk-move-ad': '前 / 后移动', + 'hk-move-qe': '左 / 右移动', + 'hk-rotate': '左 / 右旋转', + 'hk-attribute':'展示 / 隐藏属性面板', + 'hk-label': '展示 / 隐藏标签', + 'hk-rotate-head': '调整3D框朝向', + 'hk-axis': '开启 / 关闭移动', + 'hk-filter': '展示 / 隐藏选中物体外的2D结果', + 'hk-measure': '展示 / 隐藏辅助线', +}; +export { zh }; diff --git a/frontend/text-tool/src/components/Modal/sub/ModalConfirm.vue b/frontend/text-tool/src/components/Modal/sub/ModalConfirm.vue new file mode 100644 index 00000000..32a7e9dc --- /dev/null +++ b/frontend/text-tool/src/components/Modal/sub/ModalConfirm.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/frontend/text-tool/src/components/Modal/sub/ModelHelp.vue b/frontend/text-tool/src/components/Modal/sub/ModelHelp.vue new file mode 100644 index 00000000..77712afa --- /dev/null +++ b/frontend/text-tool/src/components/Modal/sub/ModelHelp.vue @@ -0,0 +1,168 @@ + + + diff --git a/frontend/text-tool/src/components/Operation/Classification/AttrValue.vue b/frontend/text-tool/src/components/Operation/Classification/AttrValue.vue new file mode 100644 index 00000000..9efcacc9 --- /dev/null +++ b/frontend/text-tool/src/components/Operation/Classification/AttrValue.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/frontend/text-tool/src/components/Operation/Classification/index.vue b/frontend/text-tool/src/components/Operation/Classification/index.vue new file mode 100644 index 00000000..6981f669 --- /dev/null +++ b/frontend/text-tool/src/components/Operation/Classification/index.vue @@ -0,0 +1,121 @@ + + + + + diff --git a/frontend/text-tool/src/components/Operation/Results/index.vue b/frontend/text-tool/src/components/Operation/Results/index.vue new file mode 100644 index 00000000..41830035 --- /dev/null +++ b/frontend/text-tool/src/components/Operation/Results/index.vue @@ -0,0 +1,213 @@ + + + + + diff --git a/frontend/text-tool/src/components/Operation/Results/lang/en.ts b/frontend/text-tool/src/components/Operation/Results/lang/en.ts new file mode 100644 index 00000000..a1d12f88 --- /dev/null +++ b/frontend/text-tool/src/components/Operation/Results/lang/en.ts @@ -0,0 +1,9 @@ +const en = { + resultsSource: 'Results Source', + labelWithoutTask: 'Without Task', + labelGroundTruth: 'Ground Truth', + labelModelRuns: 'Model Runs', + labelAll: 'All', +}; +export type ILocale = typeof en; +export { en }; diff --git a/frontend/text-tool/src/components/Operation/Results/lang/index.ts b/frontend/text-tool/src/components/Operation/Results/lang/index.ts new file mode 100644 index 00000000..0db7f505 --- /dev/null +++ b/frontend/text-tool/src/components/Operation/Results/lang/index.ts @@ -0,0 +1,2 @@ +export * from './en'; +export * from './zh'; diff --git a/frontend/text-tool/src/components/Operation/Results/lang/type.ts b/frontend/text-tool/src/components/Operation/Results/lang/type.ts new file mode 100644 index 00000000..af324dfb --- /dev/null +++ b/frontend/text-tool/src/components/Operation/Results/lang/type.ts @@ -0,0 +1 @@ +export type { ILocale } from './en'; diff --git a/frontend/text-tool/src/components/Operation/Results/lang/zh.ts b/frontend/text-tool/src/components/Operation/Results/lang/zh.ts new file mode 100644 index 00000000..211ac81a --- /dev/null +++ b/frontend/text-tool/src/components/Operation/Results/lang/zh.ts @@ -0,0 +1,10 @@ +import { ILocale } from './type'; + +const zh: ILocale = { + resultsSource: '结果来源', + labelWithoutTask: '无任务的结果', + labelGroundTruth: '人工标注', + labelModelRuns: '模型', + labelAll: '全部', +}; +export { zh }; diff --git a/frontend/text-tool/src/components/Operation/index.vue b/frontend/text-tool/src/components/Operation/index.vue new file mode 100644 index 00000000..e7348c3d --- /dev/null +++ b/frontend/text-tool/src/components/Operation/index.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/frontend/text-tool/src/components/SideView/index.vue b/frontend/text-tool/src/components/SideView/index.vue new file mode 100644 index 00000000..7cf3e068 --- /dev/null +++ b/frontend/text-tool/src/components/SideView/index.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/frontend/text-tool/src/components/SideView/lang/en.ts b/frontend/text-tool/src/components/SideView/lang/en.ts new file mode 100644 index 00000000..e1820485 --- /dev/null +++ b/frontend/text-tool/src/components/SideView/lang/en.ts @@ -0,0 +1,10 @@ +const en = { + side_overhead: 'Overhead', + side_side: 'Side', + side_near: 'Rear', + side_length: 'L', + side_width: 'W', + side_height: 'H', +}; +export type ILocale = typeof en; +export { en }; diff --git a/frontend/text-tool/src/components/SideView/lang/index.ts b/frontend/text-tool/src/components/SideView/lang/index.ts new file mode 100644 index 00000000..0db7f505 --- /dev/null +++ b/frontend/text-tool/src/components/SideView/lang/index.ts @@ -0,0 +1,2 @@ +export * from './en'; +export * from './zh'; diff --git a/frontend/text-tool/src/components/SideView/lang/type.ts b/frontend/text-tool/src/components/SideView/lang/type.ts new file mode 100644 index 00000000..af324dfb --- /dev/null +++ b/frontend/text-tool/src/components/SideView/lang/type.ts @@ -0,0 +1 @@ +export type { ILocale } from './en'; diff --git a/frontend/text-tool/src/components/SideView/lang/zh.ts b/frontend/text-tool/src/components/SideView/lang/zh.ts new file mode 100644 index 00000000..6c3bba2a --- /dev/null +++ b/frontend/text-tool/src/components/SideView/lang/zh.ts @@ -0,0 +1,11 @@ +import { ILocale } from './type'; + +const zh: ILocale = { + side_overhead: '俯视图', + side_side: '侧视图', + side_near: '后视图', + side_length: '长', + side_width: '宽', + side_height: '高', +}; +export { zh }; diff --git a/frontend/text-tool/src/components/SideView/useSideView.ts b/frontend/text-tool/src/components/SideView/useSideView.ts new file mode 100644 index 00000000..647d5002 --- /dev/null +++ b/frontend/text-tool/src/components/SideView/useSideView.ts @@ -0,0 +1,122 @@ +import { onMounted, onBeforeUnmount, reactive, toRefs, computed, Ref } from 'vue'; +import * as THREE from 'three'; +import { SideRenderView, PointCloud, axisType, Event, ResizeTransAction } from 'pc-render'; +import { IActionName } from 'pc-editor'; +import { useInjectEditor } from '../../state'; +import * as locale from './lang'; + +interface SideViewProps { + axis: axisType; +} + +export default function useSideView(dom: Ref, props: SideViewProps) { + let view = {} as SideRenderView; + let editor = useInjectEditor(); + let $$ = editor.bindLocale(locale); + let pc = editor.pc; + let actionName = '' as IActionName; + let actionTimer = -1 as any; + + let state = reactive({ + axis: props.axis, + size: new THREE.Vector3(), + // title: titleMap[props.axis as axisType], + }); + + let title = computed(() => { + let title = ''; + switch (props.axis) { + case 'z': + title = $$('side_overhead'); + break; + case '-y': + title = $$('side_side'); + break; + case '-x': + title = $$('side_near'); + break; + } + return title; + }); + + //**************life hook****************** + onMounted(() => { + if (dom.value) { + view = new SideRenderView(dom.value, pc, { + axis: state.axis, + }); + + // let action = view.getAction('resize-translate') as ResizeTransAction; + // if (action && (state.axis === '-y' || state.axis === '-x')) action.rotatable = false; + + pc.addRenderView(view); + } + + view.addEventListener(Event.RENDER_AFTER, onRender); + }); + + onBeforeUnmount(() => { + view.removeEventListener(Event.RENDER_AFTER, onRender); + }); + // ************************************ + + function onRender() { + if (!view.object) { + state.size.set(0, 0, 0); + return; + } + let box = view.object; + state.size.copy(box.scale); + } + + function onDBLclick() { + view.zoom = 1; + view.enableFit = true; + view.fitObject(); + view.render(); + } + + function onAction(name: IActionName) { + actionName = name; + handleAction(); + handleInterval(); + } + + function handleAction() { + editor.actionManager.execute(actionName); + if (!view.enableFit) { + onDBLclick(); + } + } + + function handleInterval() { + if (actionTimer >= 0) return; + + document.addEventListener('mouseup', onDocMouseUp); + actionTimer = setInterval(() => { + handleAction(); + }, 50); + } + + function onDocMouseUp() { + if (actionTimer < 0) return; + document.removeEventListener('mouseup', onDocMouseUp); + clearInterval(actionTimer); + actionTimer = -1; + } + + function clearAction() { + if (actionTimer < 0) return; + clearInterval(actionTimer); + actionTimer = -1; + } + + return { + ...toRefs(state), + $$, + title, + onDBLclick, + onAction, + clearAction, + }; +} diff --git a/frontend/text-tool/src/components/SideView/view.vue b/frontend/text-tool/src/components/SideView/view.vue new file mode 100644 index 00000000..d979b910 --- /dev/null +++ b/frontend/text-tool/src/components/SideView/view.vue @@ -0,0 +1,154 @@ + + + + + diff --git a/frontend/text-tool/src/components/Tool/Info.vue b/frontend/text-tool/src/components/Tool/Info.vue new file mode 100644 index 00000000..76e86190 --- /dev/null +++ b/frontend/text-tool/src/components/Tool/Info.vue @@ -0,0 +1,69 @@ + + + + + diff --git a/frontend/text-tool/src/components/Tool/Setting.vue b/frontend/text-tool/src/components/Tool/Setting.vue new file mode 100644 index 00000000..98de4733 --- /dev/null +++ b/frontend/text-tool/src/components/Tool/Setting.vue @@ -0,0 +1,449 @@ + + + + + diff --git a/frontend/text-tool/src/components/Tool/base.vue b/frontend/text-tool/src/components/Tool/base.vue new file mode 100644 index 00000000..b3bae13a --- /dev/null +++ b/frontend/text-tool/src/components/Tool/base.vue @@ -0,0 +1,183 @@ + + + + + diff --git a/frontend/text-tool/src/components/Tool/colorSlider.vue b/frontend/text-tool/src/components/Tool/colorSlider.vue new file mode 100644 index 00000000..9ee077df --- /dev/null +++ b/frontend/text-tool/src/components/Tool/colorSlider.vue @@ -0,0 +1,207 @@ + + + diff --git a/frontend/text-tool/src/components/Tool/index.vue b/frontend/text-tool/src/components/Tool/index.vue new file mode 100644 index 00000000..6912b129 --- /dev/null +++ b/frontend/text-tool/src/components/Tool/index.vue @@ -0,0 +1,210 @@ + + + + + diff --git a/frontend/text-tool/src/components/Tool/item.ts b/frontend/text-tool/src/components/Tool/item.ts new file mode 100644 index 00000000..02f06ad1 --- /dev/null +++ b/frontend/text-tool/src/components/Tool/item.ts @@ -0,0 +1,182 @@ +import { IState } from 'pc-editor'; +import Editor from '../../common/Editor'; +import { BsUIType as UIType } from '../../config/ui'; +import { Component } from 'vue'; +import ToolTip from './modelConfig.vue'; +import { ILocale } from './lang/type'; +export interface IItemConfig { + action: string; + // label: string; + title: ($$: (name: keyof ILocale, args?: Record) => string) => string; + getStyle?: (editor: Editor) => any; + extra?: () => Component; + hasMsg?: (editor: Editor) => boolean; + getIcon: (editor: Editor) => string; + isDisplay: (editor: Editor) => boolean; + isActive: (editor: Editor) => boolean; +} +export const allItems: IItemConfig[] = [ + { + action: 'createRect', + // label: 'Rect', + title: ($$) => $$('title_rect'), + getIcon: function (editor: Editor) { + return 'iconfont icon-biaozhunkuang'; + }, + isDisplay: function (editor: Editor) { + let state = editor.state; + return ( + state.modeConfig.ui[UIType.create2dRect] && + state.config.showSingleImgView && + state.config.projectPoint4 + ); + }, + isActive: function (editor: Editor) { + // return state.config.activeRect; + return false; + }, + }, + { + action: 'create2DBox', + title: ($$) => $$('title_create2DBox'), + getIcon: function (editor: Editor) { + return 'iconfont icon-biaozhunkuang'; + }, + isDisplay: function (editor: Editor) { + let state = editor.state; + return ( + state.modeConfig.ui[UIType.create2dBox] && + state.config.showSingleImgView && + state.config.projectPoint8 + ); + }, + isActive: function (editor: Editor) { + // return state.config.active2DBox; + return false; + }, + }, + { + action: 'create3DBox', + title: ($$) => $$('title_create3DBox'), + getIcon: function (editor: Editor) { + return 'iconfont icon-biaozhunkuang'; + }, + isDisplay: function (editor: Editor) { + let state = editor.state; + return state.modeConfig.ui[UIType.create3dBox] && !state.config.showSingleImgView; + }, + isActive: function (editor: Editor) { + // return state.config.active3DBox; + return false; + }, + }, + { + action: 'translate', + title: ($$) => $$('title_translate'), + getIcon: function (editor: Editor) { + return 'iconfont icon-yidong'; + }, + isDisplay: function (editor: Editor) { + let state = editor.state; + return state.modeConfig.ui[UIType.translate] && !state.config.showSingleImgView; + }, + isActive: function (editor: Editor) { + let state = editor.state; + return state.config.activeTranslate; + }, + }, + { + action: 'projection', + title: () => 'Projection', + getIcon: function (editor: Editor) { + return 'iconfont icon-yingshe'; + }, + isDisplay: function (editor: Editor) { + let state = editor.state; + return state.modeConfig.ui[UIType.project] && state.imgViews.length > 0; + }, + isActive: function (editor: Editor) { + return false; + }, + }, + { + action: 'reProjection', + title: () => 'Re-Projection', + getIcon: function (editor: Editor) { + return 'iconfont icon-xuanzhuan'; + }, + isDisplay: function (editor: Editor) { + let state = editor.state; + return state.modeConfig.ui[UIType.reProject] && state.imgViews.length > 0; + }, + isActive: function (editor: Editor) { + return false; + }, + }, + { + action: 'track', + title: ($$) => $$('title_track'), + getIcon: function (editor: Editor) { + return 'iconfont icon-fuzhuxian'; + }, + isDisplay: function (editor: Editor) { + let state = editor.state; + return state.modeConfig.ui[UIType.track] && state.config.showSingleImgView; + }, + isActive: function (editor: Editor) { + let state = editor.state; + return state.config.activeTrack; + }, + }, + // { + // action: 'filter2D', + // // label: 'filter2D', + // title: ($$) => $$('title_filter2D'), + // getIcon: function (editor: Editor) { + // return 'iconfont icon-shaixuan'; + // }, + // isDisplay: function (editor: Editor) { + // let state = editor.state; + // return state.modeConfig.ui[UIType.filter2D]; + // }, + // isActive: function (editor: Editor) { + // let state = editor.state; + // return state.config.filter2DByTrack; + // }, + // }, + { + action: 'model', + // label: 'Model', + title: ($$) => $$('title_model'), + hasMsg: function (editor: Editor) { + let state = editor.state; + let dataInfo = state.frames[state.frameIndex]; + return dataInfo && !!dataInfo.model && dataInfo.model.state === 'complete'; + }, + extra: () => ToolTip, + getStyle: function (editor: Editor) { + return { + 'margin-bottom': 0, + 'border-bottom-right-radius': 0, + 'border-bottom-left-radius': 0, + 'padding-bottom': 0, + }; + }, + getIcon: function (editor: Editor) { + let state = editor.state; + let dataInfo = state.frames[state.frameIndex]; + return dataInfo && !!dataInfo.model && dataInfo.model.state === 'loading' + ? 'iconfont icon-loading loading' + : 'iconfont icon-Vector'; + }, + isDisplay: function (editor: Editor) { + let state = editor.state; + return state.modeConfig.ui[UIType.rumModel] && !state.config.showSingleImgView; + }, + isActive: function (editor: Editor) { + let state = editor.state; + let dataInfo = state.frames[state.frameIndex]; + return dataInfo && !!dataInfo.model && dataInfo.model.state === 'loading'; + }, + }, +]; diff --git a/frontend/text-tool/src/components/Tool/lang/en.ts b/frontend/text-tool/src/components/Tool/lang/en.ts new file mode 100644 index 00000000..77b576e1 --- /dev/null +++ b/frontend/text-tool/src/components/Tool/lang/en.ts @@ -0,0 +1,56 @@ +const en = { + setting_display: 'Display', + setting_imgview: 'Image', + setting_rect: 'Cuboids', + setting_box: '3D Cuboids', + setting_projectbox: 'Projected Cuboids', + setting_projectpoint: 'Projected Points', + setting_pointview: 'Points', + setting_backgroundColor: 'Background Color', + setting_pointsize: 'Size', + setting_pointreset: 'Reset', + setting_colorreset: 'Reset Color', + setting_resultview: 'Objects', + setting_showlabel: 'Show Tags(M)', + setting_showannotate: 'Show Annotate(Shift+H)', + setting_pointcolor: 'Color', + setting_colorheight: 'Height', + setting_colorintensity: 'Intensity', + setting_ground: 'Ground', + + title_rect:'Create Rect', + title_create2DBox:'Create Box(2D)', + title_create3DBox:'Create Box(3D)', + title_translate:'Translate', + title_track:'Track Line', + title_filter2D:'Filter other object in 2d view', + title_model:'Run Model', + + model_title:'AI Annotation Setting', + model_name:'Model', + model_predict:'Predict all in Model', + model_select_all:'Select all', + model_unselect_all:'Unselect all', + model_confidence:'Confidence', + model_reset:'Reset', + model_classes:'Classes', + model_setting:'Setting', + model_add:'Add Results', + model_run:'Apply and Run', + + info_title: 'Scene Info', + info_datainfo: 'Coordinates', + info_pointinfo: 'Points', + info_pointall: 'Total Points', + info_pointvisible: 'Visible Points', + + utility: 'Utility', + measure: 'Distance Measure(N)', + measure_add: 'Add a Measure', + measure_radius: 'Radius(m)', + + btn_msg: 'Info', + btn_setting: 'Setting', +}; +export type ILocale = typeof en; +export { en }; diff --git a/frontend/text-tool/src/components/Tool/lang/index.ts b/frontend/text-tool/src/components/Tool/lang/index.ts new file mode 100644 index 00000000..0db7f505 --- /dev/null +++ b/frontend/text-tool/src/components/Tool/lang/index.ts @@ -0,0 +1,2 @@ +export * from './en'; +export * from './zh'; diff --git a/frontend/text-tool/src/components/Tool/lang/type.ts b/frontend/text-tool/src/components/Tool/lang/type.ts new file mode 100644 index 00000000..af324dfb --- /dev/null +++ b/frontend/text-tool/src/components/Tool/lang/type.ts @@ -0,0 +1 @@ +export type { ILocale } from './en'; diff --git a/frontend/text-tool/src/components/Tool/lang/zh.ts b/frontend/text-tool/src/components/Tool/lang/zh.ts new file mode 100644 index 00000000..6a6bac6f --- /dev/null +++ b/frontend/text-tool/src/components/Tool/lang/zh.ts @@ -0,0 +1,58 @@ +import { ILocale } from './type'; + +const zh: ILocale = { + setting_display: '显示', + setting_imgview: '图片显示', + setting_rect: '矩形框', + setting_box: '立体框', + setting_projectbox: '映射立体框', + setting_projectpoint: '映射点', + setting_pointview: '点云显示', + setting_backgroundColor: '背景色', + setting_pointsize: '点大小', + setting_pointreset: '重置', + setting_colorreset: '重置颜色', + setting_resultview: '结果显示', + setting_showlabel: '显示标签(M)', + setting_showannotate: '显示批注(Shift+H)', + setting_pointcolor: '点云颜色', + setting_colorheight: '高度', + setting_colorintensity: '强度', + setting_ground: '地面偏移', + + title_rect:'创建矩形', + title_create2DBox:'创建立方体(2D)', + title_create3DBox:'创建立方体(3D)', + title_translate:'移动', + title_track:'辅助线', + title_filter2D:'过滤2D显示', + title_model:'跑模型', + + model_title:'AI标注设置', + model_name:'模型', + model_predict:'从模型中预测', + model_select_all:'选择所有', + model_unselect_all:'反选所有', + model_confidence:'置信度', + model_classes:'标签', + model_setting:'配置', + model_reset:'重置', + model_add:'添加结果', + model_run:'设置并运行', + + + info_title: '信息', + info_datainfo: '数据信息', + info_pointinfo: '点信息', + info_pointall: '真实', + info_pointvisible: '可见', + + utility: '辅助工具', + measure: '辅助线(N)', + measure_add: '添加一条辅助线', + measure_radius: '半径(m)', + + btn_msg: '信息', + btn_setting: '显示', +}; +export { zh }; diff --git a/frontend/text-tool/src/components/Tool/modelConfig.vue b/frontend/text-tool/src/components/Tool/modelConfig.vue new file mode 100644 index 00000000..68e54d54 --- /dev/null +++ b/frontend/text-tool/src/components/Tool/modelConfig.vue @@ -0,0 +1,224 @@ + + diff --git a/frontend/text-tool/src/components/Tool/useTool.ts b/frontend/text-tool/src/components/Tool/useTool.ts new file mode 100644 index 00000000..3b5212d7 --- /dev/null +++ b/frontend/text-tool/src/components/Tool/useTool.ts @@ -0,0 +1,215 @@ +import { reactive, toRefs, watch } from 'vue'; +import { useInjectEditor } from '../../state'; +import { allItems } from './item'; +import { IActionName } from 'pc-editor'; +import { Image2DRenderView, CreateAction, Box } from 'pc-render'; +import { IModelResult, IModel } from 'pc-editor'; +import * as api from '../../api'; + +let createActions: IActionName[] = [ + 'create2DBox', + 'create2DRect', + 'createObjectWith3', + 'pickObject', +]; + +export interface IConfig { + noUtility?: boolean; + noAnnotate?: boolean; +} + +export interface IClass { + label: string; + value: string; + selected: boolean; +} +export default function useTool() { + let editor = useInjectEditor(); + let innerState = reactive({ + tools: allItems, + }); + function onTool(name: string) { + let config = editor.state.config; + switch (name) { + case 'create2DBox': + stopOtherCreateAction('create2DBox'); + editor.actionManager.execute('create2DBox'); + + break; + case 'create3DBox': + stopOtherCreateAction('createObjectWith3'); + editor.actionManager.execute('createObjectWith3'); + + break; + case 'createRect': + stopOtherCreateAction('create2DRect'); + editor.actionManager.execute('create2DRect'); + + break; + case 'translate': + editor.actionManager.execute('toggleTranslate'); + break; + case 'reProjection': + reProject(); + break; + case 'projection': + project(); + break; + case 'track': + let view = editor.pc.renderViews.find( + (e) => e.name === config.singleViewPrefix, + ) as Image2DRenderView; + let action = view.getAction('create-obj') as CreateAction; + editor.state.config.activeTrack = !editor.state.config.activeTrack; + action.trackLine = editor.state.config.activeTrack; + break; + case 'model': + onModel(); + break; + case 'filter2D': + onFilter2D(); + break; + } + } + + function onFilter2D() { + let config = editor.state.config; + config.filter2DByTrack = !config.filter2DByTrack; + editor.pc.render(); + } + + function project() { + let selection = editor.pc.selection; + + if (selection.length > 0) { + let object3D = selection.filter((e) => e instanceof Box); + if (object3D.length === 0) { + editor.showMsg('warning', 'Please Select a 3D Result'); + return; + } + editor.actionManager.execute('projectObject2D', { + createFlag: true, + updateFlag: false, + selectFlag: true, + }); + } else { + editor.actionManager.execute('projectObject2D', { + createFlag: true, + updateFlag: false, + }); + } + } + + function reProject() { + let selection = editor.pc.selection; + let object3D = selection.filter((e) => e instanceof Box); + if (object3D.length === 0) { + editor.showMsg('warning', 'Please Select a 3D Result'); + return; + } + + editor.actionManager.execute('projectObject2D', { + createFlag: true, + updateFlag: true, + selectFlag: true, + }); + } + + async function runModel() { + const modelConfig = editor.state.modelConfig; + if (!modelConfig.model) { + editor.showMsg('warning', 'Please choose Model'); + return; + } + let toolState = editor.state; + let bsState = editor.bsState; + let data = toolState.frames[toolState.frameIndex]; + let model = toolState.models.find((e) => e.name === modelConfig.model) as IModel; + const resultFilterParam = { + minConfidence: 0.5, + maxConfidence: 1, + classes: model?.classes.map((e) => e.value), + }; + if (!modelConfig.predict) { + const selectedClasses = (modelConfig.classes[modelConfig.model] || []).reduce( + (classes, item) => { + if (item.selected) { + classes.push(item.value); + } + return classes; + }, + [] as string[], + ); + if (selectedClasses.length <= 0) { + editor.showMsg('warning', 'Select at least one Class!'); + return; + } + resultFilterParam.minConfidence = modelConfig.confidence[0]; + resultFilterParam.maxConfidence = modelConfig.confidence[1]; + resultFilterParam.classes = selectedClasses; + } + let config = { + datasetId: bsState.datasetId, + dataIds: [+data.id], + modelId: +model.id, + modelVersion: model?.version, + dataType: 'SINGLE_DATA', + modelCode: model.code, + // annotationRecordId: +toolState.recordId, + resultFilterParam, + }; + // modelConfig.loading = true; + try { + let result = await api.runModel(config); + if (!result.data) throw new Error('Model Run Error'); + data.model = { + recordId: result.data, + id: model.id, + version: model.version, + state: 'loading', + }; + } catch (error: any) { + editor.showMsg('error', error.message || 'Model Run Error'); + } + // modelConfig.loading = false; + editor.dataManager.pollDataModelResult(); + } + function onModel() { + let toolState = editor.state; + let dataInfo = toolState.frames[toolState.frameIndex]; + + if (dataInfo.model && dataInfo.model.state === 'loading') return; + + // if (dataInfo.model) { + if (dataInfo.model && dataInfo.model.state === 'complete') { + let model = dataInfo.model as IModelResult; + // editor.showConfirm({ title: 'Model Results', subTitle: 'Add Results?' }).then( + // async () => { + // }, + // () => {}, + // ); + api.clearModel([+dataInfo.id], model.recordId); + editor.modelManager.addModelData(); + } else { + runModel(); + // editor.showModal('modelRun', { title: 'AI Annotation', data: {} }); + } + } + + function stopOtherCreateAction(name: string) { + if (editor.actionManager.currentAction) { + let action = editor.actionManager.currentAction; + if (action.name === name) return; + if (createActions.indexOf(action.name as IActionName) >= 0) { + editor.actionManager.stopCurrentAction(); + } + } + } + + return { + ...toRefs(innerState), + runModel, + onModel, + onTool, + }; +} diff --git a/frontend/text-tool/src/config/action.ts b/frontend/text-tool/src/config/action.ts new file mode 100644 index 00000000..76b56683 --- /dev/null +++ b/frontend/text-tool/src/config/action.ts @@ -0,0 +1,61 @@ +import { IActionName, AllActions as editorActions } from 'pc-editor'; +import * as BsActions from '../actions'; + +export type IBsActionName = keyof typeof BsActions | IActionName; +export const allActions = [...editorActions, ...Object.keys(BsActions)] as IBsActionName[]; + +export const viewHelperActions: IBsActionName[] = [ + 'toggleViewNegX', + 'toggleViewNegY', + 'toggleViewPosX', + 'toggleViewPosY', + 'toggleViewPosZ', +]; + +export const sideActions: IBsActionName[] = [ + 'translateXMinus', + 'translateXPlus', + 'translateYMinus', + 'translateYPlus', + 'translateZMinus', + 'translateZPlus', + 'rotationZRight90', + 'rotationZLeft', + 'rotationZRight', +]; + +export const generalActions: IBsActionName[] = [ + 'nextFrame', + 'preFrame', + 'toggleClassView', + 'toggleShowAnnotation', + 'toggleShowLabel', + 'toggleShowMeasure', + 'pickObject', + 'resultExpandToggle', + // 'filter2DByTrack', +]; + +export const executeActions: IBsActionName[] = [ + ...sideActions, + ...generalActions, + ...viewHelperActions, + 'create2DBox', + 'create2DRect', + 'createObjectWith3', + 'copyBackWard', + 'copyForward', + 'undo', + 'redo', + 'deleteObject', + 'focusObject', + 'projectObject2D', + 'toggleTranslate', + 'flowSave', +]; + +export const viewActions: IBsActionName[] = [ + ...generalActions, + ...viewHelperActions, + 'focusObject', +]; diff --git a/frontend/text-tool/src/config/code.ts b/frontend/text-tool/src/config/code.ts new file mode 100644 index 00000000..95e5d769 --- /dev/null +++ b/frontend/text-tool/src/config/code.ts @@ -0,0 +1,6 @@ +const Code = { + NETWORK_ERROR: 'NETWORK_ERROR', + LOGIN_INVALID: 'LOGIN_INVALID', +}; + +export default Code; diff --git a/frontend/text-tool/src/config/event.ts b/frontend/text-tool/src/config/event.ts new file mode 100644 index 00000000..77d56fb6 --- /dev/null +++ b/frontend/text-tool/src/config/event.ts @@ -0,0 +1,17 @@ +const Event = { + FLOW_ACTION: 'flow_action', + // UPDATE_RESULT_LIST: 'update_result_list', + // UPDATE_CLASS_EDIT: 'update_class_edit', + // CLEAR_MERGE_SPLIT: 'clear_merge_split', + // PLAY_FRAME_CHANGE: 'play_frame_change', + // PLAY_STOP: 'play_stop', + // PLAY_START: 'play_start', + // PRE_MERGE_ACTION: 'pre_merge_action', + // PRE_SPLIT_ACTION: 'pre_split_action', + // UPDATE_TIME_LINE: 'update_time_line', + // ANNOTATE_CHANGE: 'annotate_change', + // ANNOTATE_ADD: 'annotate_add', + // ANNOTATE_REMOVE: 'annotate_remove', +}; + +export default Event; diff --git a/frontend/text-tool/src/config/hotkey.ts b/frontend/text-tool/src/config/hotkey.ts new file mode 100644 index 00000000..c67d0218 --- /dev/null +++ b/frontend/text-tool/src/config/hotkey.ts @@ -0,0 +1,9 @@ +import { IHotkeyConfig } from 'pc-editor'; +import { IBsActionName } from './action'; + +const hotkeyConfig: IHotkeyConfig[] = [ + // flow + { key: 'ctrl+s', action: 'flowSave' }, +]; + +export default hotkeyConfig; diff --git a/frontend/text-tool/src/config/mode.ts b/frontend/text-tool/src/config/mode.ts new file mode 100644 index 00000000..3e5b4c6d --- /dev/null +++ b/frontend/text-tool/src/config/mode.ts @@ -0,0 +1,42 @@ +import { IModeConfig, OPType } from 'pc-editor'; +import { BsUIType, IBsUIType, executeUI } from './ui'; +import { IBsActionName, executeActions, viewActions } from './action'; + +function toMap(arr: T[]) { + let map = {} as Record; + arr.forEach((e) => (map[e] = true)); + return map; +} + +// execute mode +const execute: IModeConfig = { + name: 'execute', + op: OPType.EXECUTE, + ui: toMap([...executeUI]), + actions: toMap([...executeActions]), +}; + +// view mode +const view: IModeConfig = { + name: 'view', + op: OPType.VIEW, + ui: toMap([BsUIType.setting, BsUIType.info]), + actions: toMap([...viewActions]), +}; + +let modes = { + execute, + view, +}; + +export type IModeType = keyof typeof modes; +export const ModeKeys = Object.keys(modes).filter((e) => e !== 'all') as IModeType[]; + +Object.keys(modes).forEach((name) => { + let mode = modes[name as IModeType]; + mode.name = name; +}); + +// @ts-ignore +window.modes = modes; +export default modes; diff --git a/frontend/text-tool/src/config/ui.ts b/frontend/text-tool/src/config/ui.ts new file mode 100644 index 00000000..8d4284b4 --- /dev/null +++ b/frontend/text-tool/src/config/ui.ts @@ -0,0 +1,26 @@ +import { UIType } from 'pc-editor'; + +export const BsUIType = { + ...UIType, + // + rumModel: 'rumModel', + flowSave: 'flowSave', +}; +export type IBsUIType = keyof typeof BsUIType; +export const allUI = Object.keys(BsUIType) as IBsUIType[]; + +export const executeUI = [ + BsUIType.create2dBox, + BsUIType.create2dRect, + BsUIType.create3dBox, + BsUIType.sideViewTool, + BsUIType.info, + BsUIType.filter2D, + BsUIType.project, + BsUIType.reProject, + BsUIType.setting, + BsUIType.track, + BsUIType.translate, + BsUIType.rumModel, + BsUIType.flowSave, +] as IBsUIType[]; diff --git a/frontend/text-tool/src/env.d.ts b/frontend/text-tool/src/env.d.ts new file mode 100644 index 00000000..dfb2672d --- /dev/null +++ b/frontend/text-tool/src/env.d.ts @@ -0,0 +1,8 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue'; + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types + const component: DefineComponent<{}, {}, any>; + export default component; +} diff --git a/frontend/text-tool/src/hook/useContextMenu.ts b/frontend/text-tool/src/hook/useContextMenu.ts new file mode 100644 index 00000000..a6e3abc3 --- /dev/null +++ b/frontend/text-tool/src/hook/useContextMenu.ts @@ -0,0 +1,24 @@ +export default function useContextMenu() { + // oncontextmenu + let dom: HTMLDivElement | null = null; + + function handleContext(d: HTMLDivElement) { + dom = d; + dom.addEventListener('contextmenu', oncontextmenu, true); + } + + function oncontextmenu(e: Event) { + // console.log(e.target); + e.preventDefault(); + } + + function clearContext() { + dom && dom.removeEventListener('contextmenu', oncontextmenu, true); + dom = null; + } + + return { + clearContext, + handleContext, + }; +} diff --git a/frontend/text-tool/src/hook/useData.ts b/frontend/text-tool/src/hook/useData.ts new file mode 100644 index 00000000..e69de29b diff --git a/frontend/text-tool/src/hook/useFlow.ts b/frontend/text-tool/src/hook/useFlow.ts new file mode 100644 index 00000000..debb2829 --- /dev/null +++ b/frontend/text-tool/src/hook/useFlow.ts @@ -0,0 +1,82 @@ +import * as pageHandler from '../pages'; +import { useInjectEditor } from '../state'; +import { setToken } from '../api/base'; +import { getUserInfo } from '../api/common'; +import Event from '../config/event'; +import * as _ from 'lodash'; + +import useQuery from './useQuery'; +import useToken from './useToken'; + +export type IHandlerType = keyof typeof pageHandler; + +export default function UseFlow() { + let editor = useInjectEditor(); + let { bsState, state } = editor; + let query = useQuery(); + let token = useToken(); + + // datasetId=30093&dataId=352734&type=readOnly + + let mode = getMode(query); + let handler = pageHandler[mode](); + // let handler = pageHandler.dev(); + + async function init() { + iniQuery(); + + if (!token) { + editor.showMsg('error', editor.lang('not-login')); + return; + } + setToken(token); + try { + await getUserInfo(); + } catch (error) { + editor.showMsg('error', 'No user info'); + return; + } + + initFlowEvent(); + handleUnload(); + + await handler.init(); + } + + function handleUnload() { + window.addEventListener('beforeunload', (event: Event) => { + console.log('beforeunload'); + if (editor.needSave()) { + event.preventDefault(); + // @ts-ignore + event.returnValue = editor.lang('msg-not-save'); + } + }); + } + + function initFlowEvent() { + editor.addEventListener(Event.FLOW_ACTION, (data) => { + // console.log(data.data); + handler.onAction(data.data); + }); + } + + function iniQuery() { + Object.assign(bsState.query, query || {}); + bsState.recordId = (query.recordId as string) || ''; + bsState.datasetId = (query.datasetId as string) || ''; + } + + return { + init, + }; +} + +function getMode(query: Record): IHandlerType { + let mode = 'execute' as IHandlerType; + if (query.type === 'readOnly') { + mode = 'view'; + } + + return mode; +} diff --git a/frontend/text-tool/src/hook/useLang.ts b/frontend/text-tool/src/hook/useLang.ts new file mode 100644 index 00000000..59fd3619 --- /dev/null +++ b/frontend/text-tool/src/hook/useLang.ts @@ -0,0 +1,23 @@ +import { useInjectEditor } from '../state'; +import { LangType } from 'pc-editor'; + +export default function useLang() { + let editor = useInjectEditor(); + + function get>, D extends keyof T['en']>( + name: D, + locale: T, + args?: Record, + ) { + editor.getLocale(name, locale, args); + } + + function bindLocale>>(locale: T) { + return editor.bindLocale(locale); + } + + return { + get, + bindLocale, + }; +} diff --git a/frontend/text-tool/src/hook/useQuery.ts b/frontend/text-tool/src/hook/useQuery.ts new file mode 100644 index 00000000..47dc998f --- /dev/null +++ b/frontend/text-tool/src/hook/useQuery.ts @@ -0,0 +1,7 @@ +import queryString from 'query-string'; + +export default function useQuery() { + let queryStr = location.href.split('?').reverse(); + const query = queryString.parse(queryStr[0] || ''); + return query; +} diff --git a/frontend/text-tool/src/hook/useToken.ts b/frontend/text-tool/src/hook/useToken.ts new file mode 100644 index 00000000..7f725bf4 --- /dev/null +++ b/frontend/text-tool/src/hook/useToken.ts @@ -0,0 +1,19 @@ +import Cookies from 'js-cookie'; + +window.Cookies = Cookies; + +let hostname = document.location.hostname || document.location.host; +let dot = hostname.indexOf('.'); +let parentHost = isIp(hostname) ? hostname : hostname.substring(dot + 1); + +export default function useToken() { + let token = Cookies.get(`${parentHost} token`); + token = token ? `Bearer ${token}` : ''; + return token; +} + + + +function isIp(str:string = ''){ + return /((25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))\.){3}(25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))/.test(str); +} \ No newline at end of file diff --git a/frontend/text-tool/src/hook/useTool.ts b/frontend/text-tool/src/hook/useTool.ts new file mode 100644 index 00000000..fd19ce49 --- /dev/null +++ b/frontend/text-tool/src/hook/useTool.ts @@ -0,0 +1,90 @@ +import { useInjectEditor } from '../state'; +import * as api from '../api'; +import { BSError } from 'pc-editor'; + +export default function useTool() { + let editor = useInjectEditor(); + let { bsState, state } = editor; + + async function loadRecord() { + try { + let { dataInfos, datasetId } = await api.getInfoByRecordId(bsState.recordId); + // state.isSeriesFrame = isSeriesFrame; + bsState.datasetId = datasetId; + editor.dataManager.setSceneData(dataInfos); + loadSceneData(0); + } catch (error) { + throw new BSError('', editor.lang('load-record-error'), error); + } + } + // 加载连续帧sceneData + function loadSceneData(index: number) { + const { bsState } = editor; + // bsState.sceneIndex = index; + // bsState.seriesFrameId = bsState.seriesFrames[index] || ''; + let id = bsState.seriesFrameId || '-1'; + let sceneFrames = editor.dataManager.getFramesBySceneId(id); + if (sceneFrames.length === 0) return; + editor.setFrames(sceneFrames); + editor.loadFrame(0, true, true); + } + + + + + + + + //////////////////////////////// + + async function loadClasses() { + try { + let config = await api.getDataSetClass(bsState.datasetId); + // test + // if (config.length > 0) { + // config[0].constraint = true; + // config[0].size3D = new THREE.Vector3(4, 4, 4); + // } + editor.setClassTypes(config); + } catch (error) { + throw new BSError('', editor.lang('load-class-error'), error); + } + } + + async function loadModels() { + try { + let models = await api.getModelList(); + editor.state.models = models; + } catch (error) { + throw new BSError('', editor.lang('load-model-error'), error); + } + } + + async function loadDateSetClassification() { + try { + let classifications = await api.getDataSetClassification(bsState.datasetId); + editor.state.classifications = classifications; + } catch (error) { + throw new BSError('', editor.lang('load-dataset-classification-error'), error); + } + } + + async function loadDataSetInfo() { + try { + let datasetId = editor.bsState.datasetId; + let data = await api.getDataSetInfo(datasetId); + bsState.datasetName = data.name; + bsState.datasetType = data.type; + } catch (error) { + throw new BSError('', 'load data-set info error', error); + } + } + + return { + loadModels, + loadClasses, + loadDataSetInfo, + loadRecord, + loadDateSetClassification, + }; +} diff --git a/frontend/text-tool/src/hook/useUI.ts b/frontend/text-tool/src/hook/useUI.ts new file mode 100644 index 00000000..4e1e7f8e --- /dev/null +++ b/frontend/text-tool/src/hook/useUI.ts @@ -0,0 +1,39 @@ +import { useInjectEditor } from '../state'; +import { OPType, StatusType } from 'pc-editor'; +import { IBsUIType } from '../config/ui'; + +export default function useUI() { + let editor = useInjectEditor(); + let state = editor.state; + + function canEdit() { + return state.modeConfig.op === OPType.EXECUTE || state.modeConfig.name === 'all'; + } + function canAnnotate() { + return state.modeConfig.op === OPType.VERIFY || state.modeConfig.name === 'all'; + } + + function canOperate() { + return state.status !== StatusType.Create; + } + + function isPlay() { + return state.status === StatusType.Play; + } + function isCheck() { + return state.config.showCheckView; + } + + function has(name: IBsUIType | string) { + return state.modeConfig.ui[name]; + } + + return { + canEdit, + canAnnotate, + canOperate, + isPlay, + isCheck, + has, + }; +} diff --git a/frontend/text-tool/src/lang/en.ts b/frontend/text-tool/src/lang/en.ts new file mode 100644 index 00000000..63c2d84e --- /dev/null +++ b/frontend/text-tool/src/lang/en.ts @@ -0,0 +1,39 @@ +const en = { + // load + 'load-resource-error': 'Load Resource Error', + 'load-object-error': 'Load Object Error', + 'load-classification-error': 'Load Classification Error', + 'load-class-error': 'Load Class Error', + 'load-model-error': 'Load Models Error', + 'load-dataset-classification-error': 'Load DataSet Classification Error', + 'load-record-error': 'Load Record Error', + 'load-frame-series-error': 'Load FrameSeries Data Error', + 'invalid-query': 'Invalid Query', + 'load-error': 'Load Error', + + // model + 'load-track': 'Tracking....', + 'track-no-data': "No Tracking object found, please check your objects' location and direction", + 'track-error': 'Track Error', + 'track-ok': 'Track Success', + + // info + 'load-point': 'Loading....', + 'save-ok': 'Save Success', + 'save-error': 'Save Error', + 'model-run-error': 'Model Run Error', + 'model-run-no-data': 'No Model Results', + 'no-point-data': 'No PointCloud Data', + 'play-error': 'Play Error', + 'unknown-error': 'Error', + 'network-error': 'Network Error', + 'login-invalid': 'Login Invalid', + 'not-login': 'Not logged in', + + // msg + 'msg-not-save': "You didn't save changes?", +}; + +export type ILocale = typeof en; + +export { en }; diff --git a/frontend/text-tool/src/lang/index.ts b/frontend/text-tool/src/lang/index.ts new file mode 100644 index 00000000..74ea3d05 --- /dev/null +++ b/frontend/text-tool/src/lang/index.ts @@ -0,0 +1,2 @@ +export * from './zh'; +export * from './en'; diff --git a/frontend/text-tool/src/lang/type.ts b/frontend/text-tool/src/lang/type.ts new file mode 100644 index 00000000..af324dfb --- /dev/null +++ b/frontend/text-tool/src/lang/type.ts @@ -0,0 +1 @@ +export type { ILocale } from './en'; diff --git a/frontend/text-tool/src/lang/zh.ts b/frontend/text-tool/src/lang/zh.ts new file mode 100644 index 00000000..b9138d35 --- /dev/null +++ b/frontend/text-tool/src/lang/zh.ts @@ -0,0 +1,38 @@ +import { ILocale } from './type'; + +const zh: ILocale = { + 'load-resource-error': '加载资源失败', + 'load-object-error': '加载结果失败', + 'load-classification-error': '加载分类信息失败', + 'load-class-error': '加载标签失败', + 'load-model-error': '加载模型失败', + 'load-dataset-classification-error': '加载数据集分类失败', + 'load-record-error': '加载标注信息失败', + 'load-frame-series-error': '加载连续帧数据失败', + 'invalid-query': '参数不合法', + 'load-error': '加载失败', + + // model + 'load-track': '跟踪中....', + 'track-no-data': '无追踪结果,请检查你的结果信息和追踪方向是否正确', + 'track-error': '追踪错误', + 'track-ok': '追踪成功', + + // info + 'load-point': '加载点云....', + 'save-ok': '保存成功', + 'save-error': '保存失败', + 'model-run-error': '模型运行异常', + 'model-run-no-data': '无模型结果', + 'no-point-data': '无点云数据', + 'play-error': '播放异常', + 'unknown-error': '异常错误', + 'network-error': '网络错误', + 'login-invalid': '登录过期', + 'not-login': '未登录', + + // msg + 'msg-not-save': '是否保存变更?', +}; + +export { zh }; diff --git a/frontend/text-tool/src/main.ts b/frontend/text-tool/src/main.ts new file mode 100644 index 00000000..d32f6bba --- /dev/null +++ b/frontend/text-tool/src/main.ts @@ -0,0 +1,20 @@ +import { createApp, DefineComponent } from 'vue'; +import Antd from 'ant-design-vue'; +import 'ant-design-vue/dist/antd.dark.css'; + +import Vue3ColorPicker from 'vue3-colorpicker'; +import 'vue3-colorpicker/style.css'; + +import './style/index.less'; + +import App from './App.vue'; + +export function init(App: DefineComponent<{}, {}, any>) { + const app = createApp(App); + app.use(Antd); + app.use(Vue3ColorPicker); + + app.mount('#app'); +} + +init(App); diff --git a/frontend/text-tool/src/packages/pc-editor/Editor.ts b/frontend/text-tool/src/packages/pc-editor/Editor.ts new file mode 100644 index 00000000..472759d4 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/Editor.ts @@ -0,0 +1,312 @@ +import CmdManager from './common/CmdManager'; +import HotkeyManager from './common/HotkeyManager'; +import ActionManager from './common/ActionManager'; +import ViewManager from './common/ViewManager'; +import ConfigManager from './common/ConfigManager'; +import DataManager from './common/DataManager'; +import BusinessManager from './common/BusinessManager'; +import LoadManager from './common/LoadManager'; +import DataResource from './common/DataResource'; +import ModelManager from './common/ModelManager'; + +import { getDefaultState } from './state'; +import type { IState } from './state'; +import { + IUserData, + IClassType, + IImgViewConfig, + LangType, + IFrame, + Const, + IResultSource, +} from './type'; +import { IModeType, OPType, IModeConfig } from './config/type'; +import * as _ from 'lodash'; +import * as THREE from 'three'; +import Event from './config/event'; +import { nanoid } from 'nanoid'; +import Mustache from 'mustache'; +import BSError from './common/BSError'; +import * as locale from './lang'; +import * as utils from './utils'; +import { RegisterFn, ModalFn, MsgFn, ConfirmFn, LoadingFn } from './uitype'; + +type LocaleType = typeof locale; + +export default class Editor extends THREE.EventDispatcher { + idCount: number = 1; + state: IState; + // currentTrack: string | null = null; + frameMap: Map = new Map(); + frameIndexMap: Map = new Map(); + classMap: Map = new Map(); + needUpdateFilter: boolean = true; + eventSource: string = ''; + + cmdManager: CmdManager; + hotkeyManager: HotkeyManager; + actionManager: ActionManager; + viewManager: ViewManager; + configManager: ConfigManager; + dataManager: DataManager; + businessManager: BusinessManager; + // playManager: PlayManager; + loadManager: LoadManager; + dataResource: DataResource; + modelManager: ModelManager; + // trackManager: TrackManager; + + // ui + registerModal: RegisterFn = () => {}; + showModal: ModalFn = () => Promise.resolve(); + showMsg: MsgFn = () => {}; + showConfirm: ConfirmFn = () => Promise.resolve(); + showLoading: LoadingFn = () => {}; + + constructor() { + super(); + + this.state = getDefaultState(); + + this.cmdManager = new CmdManager(this); + this.hotkeyManager = new HotkeyManager(this); + this.actionManager = new ActionManager(this); + this.viewManager = new ViewManager(this); + this.configManager = new ConfigManager(this); + this.dataManager = new DataManager(this); + // this.playManager = new PlayManager(this); + this.loadManager = new LoadManager(this); + this.dataResource = new DataResource(this); + this.businessManager = new BusinessManager(this); + this.modelManager = new ModelManager(this); + // this.trackManager = new TrackManager(this) + + this.initEvent(); + + // util + this.blurPage = _.throttle(this.blurPage.bind(this), 40); + } + + initEvent() { + } + + // locale + lang(name: T, args?: Record) { + return this.getLocale(name, locale, args); + } + + getLocale>, D extends keyof T['en']>( + name: D, + locale: T, + args?: Record, + ) { + let lang = this.state.lang; + let langObject = locale[lang]; + if (!langObject) return ''; + let msg = langObject[name as any] || ''; + if (args) { + msg = Mustache.render(msg, args); + } + return msg; + } + + bindLocale>>(locale: T) { + let bindGet = (name: D, args?: Record) => { + return this.getLocale(name, locale, args); + }; + return bindGet; + } + + withEventSource(source: string, fn: () => void) { + this.eventSource = source; + try { + fn(); + } catch (e: any) {} + this.eventSource = ''; + } + + async loadFrame(index: number, showLoading: boolean = true, force: boolean = false) { + await this.loadManager.loadFrame(index, showLoading, force); + } + + getCurrentFrame() { + return this.state.frames[this.state.frameIndex]; + } + // trackId + createTrackId() { + return nanoid(16); + } + + // trackName + getId() { + // return THREE.MathUtils.generateUUID(); + return this.idCount++ + ''; + } + + // setCurrentTrack(trackId: string | null = null) { + // if (this.currentTrack !== trackId) { + // this.currentTrack = trackId; + // this.dispatchEvent({ type: Event.CURRENT_TRACK_CHANGE, data: this.currentTrack }); + // } + // } + getCurTrack() { + } + // create + createAnnotate3D( + position: THREE.Vector3, + scale: THREE.Vector3, + rotation: THREE.Euler, + userData: IUserData = {}, + ) { + let object = utils.createAnnotate3D(this, position, scale, rotation, userData); + utils.setIdInfo(this, object.userData); + return object; + } + + createAnnotateRect(center: THREE.Vector2, size: THREE.Vector2, userData: IUserData = {}) { + let object = utils.createAnnotateRect(this, center, size, userData); + utils.setIdInfo(this, object.userData); + return object; + } + + createAnnotateBox2D(positions1: any, positions2: any, userData: IUserData = {}) { + let object = utils.createAnnotateBox2D(this, positions1, positions2, userData); + utils.setIdInfo(this, object.userData); + return object; + } + + setFrames(frames: IFrame[]) { + this.frameMap.clear(); + this.state.frames = frames; + frames.forEach((e, index) => { + this.frameMap.set(e.id + '', e); + this.frameIndexMap.set(e.id + '', index); + }); + } + + getFrameIndex(frameId: string) { + return this.frameIndexMap.get(frameId + '') as number; + } + getFrame(frameId: string) { + return this.frameMap.get(frameId + '') as IFrame; + } + + getObjectUserData(object: any, frame?: IFrame) { + // let { isSeriesFrame } = this.state; + + let userData = object.userData as Required; + let trackId = userData.trackId as string; + // if (isSeriesFrame) { + // let globalTrack = this.trackManager.getTrackObject(trackId) || {}; + // Object.assign(userData, globalTrack); + // } + return userData; + } + + updateObjectRenderInfo(objects: any) { + } + + // set get + setClassTypes(classTypes: IClassType[]) { + this.classMap.clear(); + this.state.classTypes = classTypes; + classTypes.forEach((e) => { + this.classMap.set(e.name + '', e); + this.classMap.set(e.id + '', e); + }); + } + + getClassType(name: string | IUserData) { + if (name instanceof Object) { + let { classId, classType } = name; + let key = classId || classType; + return this.classMap.get(key + '') as IClassType; + } else { + return this.classMap.get(name + '') as IClassType; + } + } + async getResultSources(frame?: IFrame): Promise {} + setSources(sources: IResultSource[]) { + if (!sources) return; + let { FILTER_ALL, withoutTaskId } = this.state.config; + this.state.sources = sources; + + let sourceMap = {}; + sources.forEach((e) => { + sourceMap[e.sourceId] = true; + }); + + this.state.sourceFilters = this.state.sourceFilters.filter((e) => sourceMap[e]); + if (this.state.sourceFilters.length === 0) this.state.sourceFilters = [FILTER_ALL]; + } + setMode(modeConfig: IModeConfig) { + // this.state.mode = modeConfig.name || ''; + this.state.modeConfig = modeConfig; + this.hotkeyManager.setHotKeyFromAction(this.state.modeConfig.actions); + this.viewManager.updateViewStatus(); + } + + focusObject(object: THREE.Object3D) { + let view = this.viewManager.getMainView(); + view.focusPosition(object.position); + } + + focusPosition(position: THREE.Vector3) { + let view = this.viewManager.getMainView(); + view.focusPosition(position); + } + + setPointCloudData(data: any, ground: number, intensityRange?: [number, number]) { + // this.dispatchEvent({ type: Event.LOAD_POINT_AFTER }); + } + + frameChange(frames?: IFrame | IFrame[]) { + frames = frames || this.state.frames; + if (!Array.isArray(frames)) frames = [frames]; + + frames.forEach((frame) => { + frame.needSave = true; + }); + } + + handleErr(err: BSError | Error, message: string = '') { + if (err instanceof BSError) { + utils.handleError(this, err); + } else { + utils.handleError(this, new BSError('', message, err)); + } + } + + updateIDCounter() { + let id = this.dataManager.getMaxId(); + this.idCount = id + 1; + } + + reset() { + this.state.frameIndex = -1; + this.state.frames = []; + this.dataManager.clear(); + this.dataResource.clear(); + } + + clear() { + } + + toggleTranslate(flag: boolean, object?: THREE.Object3D) { + } + + blurPage() { + if (document.activeElement && document.activeElement !== document.body) { + (document.activeElement as any).blur(); + } + } + + selectObject(objects?: any) { + } + + selectByTrackId(trackId: string) { + } + + updateSelect() { + } +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/create.ts b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/create.ts new file mode 100644 index 00000000..c120ed6c --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/create.ts @@ -0,0 +1,147 @@ +import { MainRenderView, CreateAction, Image2DRenderView, Box, Points } from 'pc-render'; +import * as THREE from 'three'; +import * as _ from 'lodash'; +import Editor from '../../../Editor'; +import { define } from '../define'; +import { getTransformFrom3Point, getMiniBox, getMiniBox1 } from '../../../utils'; +import { IAnnotationInfo, StatusType, IUserData, Const, IObject } from '../../../type'; +import EditorEvent from '../../../config/event'; + +export const createObjectWith3 = define({ + valid(editor: Editor) { + let state = editor.state; + return !state.config.showSingleImgView && state.modeConfig.actions['createObjectWith3']; + }, + end(editor: Editor) { + let action = this.action as CreateAction; + action.end(); + editor.state.status = StatusType.Default; + }, + execute(editor: Editor) { + let view = editor.pc.renderViews.find((e) => e instanceof MainRenderView) as MainRenderView; + if (view) { + let action = view.getAction('create-obj') as CreateAction; + this.action = action; + + editor.state.status = StatusType.Create; + + return new Promise((resolve) => { + action.start({ type: 'points-3' }, (data: THREE.Vector2[]) => { + // console.log(data); + let projectPos = data.map((e) => view.canvasToWorld(e)); + let transform = getTransformFrom3Point(projectPos); + // console.log(projectPos); + + transform.scale.z = 2; + transform.position.z = editor.pc.ground.plane.constant + 1; + // transform.position.z = 1; + + let points = editor.pc.groupPoints.children[0] as Points; + let positions = points.geometry.attributes['position'] as THREE.BufferAttribute; + getMiniBox1(transform, positions, editor.state.config.heightRange); + // getMiniBox(transform, positions); + + transform.scale.x = Math.max(0.2, transform.scale.x); + transform.scale.y = Math.max(0.2, transform.scale.y); + transform.scale.z = Math.max(0.2, transform.scale.z); + // debugger; + + let userData = {} as IUserData; + // userData.resultType = Const.Dynamic; + // userData.resultStatus = Const.True_Value; + + let box = editor.createAnnotate3D( + transform.position, + transform.scale, + transform.rotation, + userData, + ); + + let trackObject: Partial = { + trackId: userData.trackId, + trackName: userData.trackName, + // resultType: userData.resultType, + // resultStatus: userData.resultStatus, + }; + + editor.state.config.showClassView = true; + + editor.cmdManager.withGroup(() => { + editor.cmdManager.execute('add-object', box); + // if (editor.state.isSeriesFrame) { + // editor.cmdManager.execute('add-track', trackObject); + // } + + editor.cmdManager.execute('select-object', box); + }); + + resolve(box); + }); + }); + } + }, +}); + +export const createAnnotation = define({ + valid(editor: Editor) { + let state = editor.state; + return !state.config.showSingleImgView && state.modeConfig.actions['createAnnotation']; + }, + end(editor: Editor) { + let action = this.action as CreateAction; + if (action) action.end(); + + editor.showModal(false); + editor.state.status = StatusType.Default; + }, + execute(editor: Editor, args?: { object: Box }) { + let view = editor.pc.renderViews.find((e) => e instanceof MainRenderView) as MainRenderView; + let state = editor.state; + + editor.state.status = StatusType.Create; + + if (view) { + return new Promise(async (resolve) => { + if (args && args.object) { + this.action = null; + await create(args.object); + resolve(null); + } else { + let action = view.getAction('create-obj') as CreateAction; + this.action = action; + + action.start( + { type: 'points-1', trackLine: false }, + async (data: THREE.Vector2[]) => { + let obj = view.getObjectByCanvas(data[0]); + if (obj) { + await create(obj); + } else { + await create(view.canvasToWorld(data[0])); + } + resolve(null); + }, + ); + } + }); + } + + async function create(data: THREE.Object3D | THREE.Vector3) { + let result; + let isObject = data instanceof THREE.Object3D; + let object = data as THREE.Object3D; + let custom = isObject + ? { id: object.userData.id, uuid: object.uuid } + : (data as THREE.Vector3).clone(); + + try { + result = await editor.showModal('annotation', { + title: '', + data: { type: isObject ? 'object' : 'position', custom }, + }); + } catch (e) { + console.log('cancel'); + } + } + }, +}); diff --git a/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/create2D.ts b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/create2D.ts new file mode 100644 index 00000000..7d8f7e8d --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/create2D.ts @@ -0,0 +1,392 @@ +import { + Rect, + CreateAction, + Image2DRenderView, + Box, + Box2D, + AnnotateObject, + utils, +} from 'pc-render'; +import * as THREE from 'three'; +import * as _ from 'lodash'; +import Editor from '../../../Editor'; +import { define } from '../define'; +import { setIdInfo, clamRange } from '../../../utils'; +import { Const, IAnnotationInfo, IUserData, StatusType } from '../../../type'; + +export const create2DRect = define({ + valid(editor: Editor) { + let state = editor.state; + return ( + state.config.showSingleImgView && + state.modeConfig.actions['create2DRect'] && + state.config.projectPoint4 + ); + }, + end(editor: Editor) { + let action = this.action as CreateAction; + action.end(); + editor.state.status = StatusType.Default; + }, + execute(editor: Editor) { + let config = editor.state.config; + let view = editor.pc.renderViews.find( + (e) => e.name === config.singleViewPrefix, + ) as Image2DRenderView; + // let view = editor.activeView as Image2DRenderView; + if (view) { + let action = view.getAction('create-obj') as CreateAction; + this.action = action; + editor.state.status = StatusType.Create; + + return new Promise((resolve) => { + action.start( + { + type: 'box', + trackLine: config.activeTrack, + // startMouseDown: true, + // startClick: false, + }, + (data: THREE.Vector2[]) => { + // console.log(data); + if (data.length !== 2) { + return resolve(null); + } + + // to 图片坐标系 + data.forEach((pos) => { + view.domToImg(pos); + }); + + if ( + config.limitRect2Image && + noPointsInImage(data[0], data[1], view.imgSize) + ) { + editor.showMsg('warning', editor.lang('create-rect-valid')); + resolve(null); + return; + } + + config.limitRect2Image && clipPoints(data, view.imgSize); + + let center = new THREE.Vector2(); + center.copy(data[0]).add(data[1]).divideScalar(2); + let size = new THREE.Vector2(); + + size.copy(data[0]).sub(data[1]); + size.x = Math.abs(size.x); + size.y = Math.abs(size.y); + + let rect = editor.createAnnotateRect(center, size, { + resultStatus: Const.True_Value, + resultType: Const.Dynamic, + } as IUserData); + let userData = rect.userData; + rect.viewId = view.renderId || view.id; + editor.state.config.showClassView = true; + + editor.cmdManager.withGroup(() => { + editor.cmdManager.execute('add-object', [{ objects: rect }]); + editor.cmdManager.execute('select-object', rect); + }); + + resolve(rect); + }, + ); + }); + } + }, +}); + +export const create2DBox = define({ + valid(editor: Editor) { + let state = editor.state; + return ( + editor.state.config.showSingleImgView && + state.modeConfig.actions['create2DBox'] && + state.config.projectPoint8 + ); + }, + isContinue(editor: Editor) { + return editor.state.config.active2DBox; + }, + end(editor: Editor) { + let action = this.action as CreateAction; + action.end(); + editor.state.status = StatusType.Default; + }, + execute(editor: Editor) { + let config = editor.state.config; + let view = editor.pc.renderViews.find( + (e) => e.name === config.singleViewPrefix, + ) as Image2DRenderView; + // let view = editor.activeView as Image2DRenderView; + if (view) { + let action = view.getAction('create-obj') as CreateAction; + this.action = action; + editor.state.status = StatusType.Create; + + return new Promise((resolve) => { + action.start( + { + type: 'box-2', + trackLine: config.activeTrack, + // startMouseDown: true, + // startClick: false, + }, + (data: THREE.Vector2[]) => { + // to 图片坐标系 + data.forEach((pos) => { + view.domToImg(pos); + }); + + let positions1 = getPositionFromPoints([data[0], data[1]]); + let positions2 = getPositionFromPoints([data[2], data[3]]); + + let box = editor.createAnnotateBox2D( + positions1 as any, + positions2 as any, + { + resultStatus: Const.True_Value, + resultType: Const.Dynamic, + } as IUserData, + ); + let userData = box.userData; + box.viewId = view.renderId || view.id; + + editor.state.config.showClassView = true; + editor.cmdManager.withGroup(() => { + editor.cmdManager.execute('add-object', [{ objects: box }]); + editor.cmdManager.execute('select-object', box); + }); + + resolve(box); + }, + ); + }); + } + }, +}); + +export const projectObject2D = define({ + valid(editor: Editor) { + return true; + }, + execute( + editor: Editor, + args: { updateFlag?: boolean; createFlag?: boolean; selectFlag?: boolean } = {}, + ) { + let { updateFlag = true, createFlag = true, selectFlag = false } = args; + let selection = editor.pc.selection; + let config = editor.state.config; + let addObjects: AnnotateObject[] = []; + let deleteObjects: AnnotateObject[] = []; + if (!config.projectPoint4 && !config.projectPoint8) return; + + let annotate3D = editor.pc.getAnnotate3D(); + let annotate2D = editor.pc.getAnnotate2D(); + let views = editor.pc.renderViews.filter((e) => + e.name.startsWith(`${config.imgViewPrefix}`), + ) as Image2DRenderView[]; + + // 映射选中的Box + if (selectFlag && selection.length === 1 && selection[0] instanceof Box) { + annotate3D = [selection[0]]; + } + + let existMapRect = {} as Record>; + let existMapBox2D = {} as Record>; + views.forEach((view) => { + existMapRect[view.id] = {} as Record; + existMapBox2D[view.id] = {} as Record; + }); + annotate2D.forEach((object) => { + let viewId = object.viewId; + let userData = object.userData as IUserData; + if ( + object instanceof Rect && + existMapRect[viewId] && + // userData.isProjection && + userData.trackId + ) { + existMapRect[viewId][userData.trackId] = object; + } + + if ( + object instanceof Box2D && + existMapBox2D[viewId] && + // userData.isProjection && + userData.trackId + ) { + existMapBox2D[viewId][userData.trackId] = object; + } + }); + + let updateN = 0; + annotate3D.forEach((object) => { + let userData = object.userData as IUserData; + // let objectId = userData.id as string; + let trackId = userData.trackId as string; + views.forEach((view) => { + let viewId = view.id; + + // isBoxInImage + if (utils.isBoxInImage(object, view)) { + if (config.projectPoint8) { + if (existMapBox2D[viewId][trackId]) { + if (updateFlag) { + updateProjectBox(view, object, existMapBox2D[viewId][trackId]); + updateN++; + } + } else { + if (createFlag) { + createProjectBox2D(view, object); + } + } + } + + if (config.projectPoint4) { + if (existMapRect[viewId][trackId]) { + if (updateFlag) { + updateProjectRect(view, object, existMapRect[viewId][trackId]); + updateN++; + } + } else { + if (createFlag) { + createProjectRect(view, object); + } + } + } + } else { + if (existMapRect[viewId][trackId]) + deleteObjects.push(existMapRect[viewId][trackId]); + if (existMapBox2D[viewId][trackId]) + deleteObjects.push(existMapBox2D[viewId][trackId]); + } + }); + }); + + console.log('updateN:', updateN); + console.log('addObjects:', addObjects); + console.log('deleteObjects:', deleteObjects); + + if (addObjects.length > 0 || deleteObjects.length > 0) { + editor.cmdManager.withGroup(() => { + if (deleteObjects.length > 0) + editor.cmdManager.execute('delete-object', deleteObjects); + if (addObjects.length > 0) editor.cmdManager.execute('add-object', addObjects); + }); + } + + function updateProjectRect(view: Image2DRenderView, object: Box, target: Rect) { + let info1 = view.getBoxRect(object); + target.center.copy(info1.center); + target.size.copy(info1.size); + // TODO: + if (config.limitRect2Image) { + validRect(view, target); + } + + editor.pc.render(); + } + + function validRect(view: Image2DRenderView, target: Rect) { + let center = target.center; + let size = target.size; + + let rx = THREE.MathUtils.clamp(center.x + size.x / 2, 0, view.imgSize.x); + let lx = THREE.MathUtils.clamp(center.x - size.x / 2, 0, view.imgSize.x); + let ty = THREE.MathUtils.clamp(center.y - size.y / 2, 0, view.imgSize.y); + let by = THREE.MathUtils.clamp(center.y + size.y / 2, 0, view.imgSize.y); + target.center.set((rx + lx) / 2, (by + ty) / 2); + target.size.set(rx - lx, by - ty); + } + + function updateProjectBox(view: Image2DRenderView, object: Box, target: Box2D) { + let info2 = view.getBox2DBox(object); + target.copyVector2Of4(info2.positionsFront as any, target.positions1); + target.copyVector2Of4(info2.positionsBack as any, target.positions2); + // TODO: + editor.pc.render(); + } + + function createProjectRect(view: Image2DRenderView, object: Box) { + let info1 = view.getBoxRect(object); + let object2D1 = new Rect(); + object2D1.center.copy(info1.center); + object2D1.size.copy(info1.size); + if (config.limitRect2Image) { + validRect(view, object2D1); + } + setObject(view, object, object2D1); + } + function createProjectBox2D(view: Image2DRenderView, object: Box) { + let info2 = view.getBox2DBox(object); + let object2D = new Box2D(); + object2D.copyVector2Of4(info2.positionsFront as any, object2D.positions1); + object2D.copyVector2Of4(info2.positionsBack as any, object2D.positions2); + setObject(view, object, object2D); + } + function setObject(view: Image2DRenderView, object: Box, object2D: Box2D | Rect) { + object2D.color = object.color.getStyle(); + object2D.viewId = view.id; + object2D.connectId = object.id; + + let userData = object2D.userData as IUserData; + userData.classType = object.userData.classType; + userData.classId = object.userData.classId; + userData.modelClass = object.userData.modelClass; + userData.trackId = object.userData.trackId; + userData.trackName = object.userData.trackName; + + setIdInfo(editor, userData); + userData.isProjection = true; + + addObjects.push(object2D); + + // editor.pc.addObject(object2D as any); + } + }, +}); + +function noPointsInImage(p1: THREE.Vector2, p2: THREE.Vector2, imgSize: THREE.Vector2) { + let n = 0; + [p1, p2, new THREE.Vector2(p1.x, p2.y), new THREE.Vector2(p2.x, p1.y)].forEach((pos) => { + if (pos.x > 0 && pos.x < imgSize.x && pos.y > 0 && pos.y < imgSize.y) n++; + }); + return n === 0; +} + +function clipPoints(points: THREE.Vector2[], imgSize: THREE.Vector2) { + points.forEach((pos) => { + pos.x = clamRange(pos.x, 0, imgSize.x); + pos.y = clamRange(pos.y, 0, imgSize.y); + }); +} + +function get2DBox(points: THREE.Vector2[]) { + let minX = Infinity; + let maxX = -Infinity; + let minY = Infinity; + let maxY = -Infinity; + points.forEach((p) => { + if (p.x < minX) minX = p.x; + if (p.x > maxX) maxX = p.x; + if (p.y < minY) minY = p.y; + if (p.y > maxY) maxY = p.y; + }); + + return { minX, minY, maxX, maxY }; +} + +function getPositionFromPoints(points: THREE.Vector2[]) { + let { minX, minY, maxX, maxY } = get2DBox(points); + let positions = [ + new THREE.Vector2(minX, minY), + new THREE.Vector2(minX, maxY), + new THREE.Vector2(maxX, maxY), + new THREE.Vector2(maxX, minY), + ]; + + return positions; +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/frame.ts b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/frame.ts new file mode 100644 index 00000000..bf80851a --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/frame.ts @@ -0,0 +1,25 @@ +import { Box, MainRenderView, TransformControlsAction } from 'pc-render'; +import { define } from '../define'; +import Editor from '../../../Editor'; + +export const nextFrame = define({ + // valid(editor: Editor) { + // return editor.state.isSeriesFrame; + // }, + execute(editor: Editor) { + // editor.tool + const { frameIndex } = editor.state; + let toIndex = frameIndex + 1; + editor.loadFrame(toIndex); + }, +}); +export const preFrame = define({ + // valid(editor: Editor) { + // return editor.state.isSeriesFrame; + // }, + execute(editor: Editor) { + const { frameIndex } = editor.state; + let toIndex = frameIndex - 1; + editor.loadFrame(toIndex); + }, +}); diff --git a/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/general.ts b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/general.ts new file mode 100644 index 00000000..56de65f5 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/general.ts @@ -0,0 +1,140 @@ +import { Box, Image2DRenderView, MainRenderView, TransformControlsAction } from 'pc-render'; +import { define } from '../define'; +import Editor from '../../../Editor'; +import { IAnnotationInfo, StatusType } from '../../../type'; +import { CreateAction } from 'pc-render'; +import Event from '../../../config/event'; +// import * as THREE from 'three'; + +export const toggleTranslate = define({ + valid(editor: Editor) { + return true; + }, + execute(editor: Editor) { + let selection = editor.pc.selection; + let config = editor.state.config; + let object = selection.find((annotate) => annotate instanceof Box); + + if (object) { + if (config.activeTranslate) { + editor.toggleTranslate(false); + } else { + editor.toggleTranslate(true, object as any); + } + } + config.activeTranslate = !config.activeTranslate; + }, +}); + +export const focusObject = define({ + valid(editor: Editor) { + let box = editor.pc.selection.find((annotate) => annotate instanceof Box); + return !!box; + }, + execute(editor: Editor) { + editor.focusObject(editor.pc.selection.find((annotate) => annotate instanceof Box) as Box); + }, +}); + +export const deleteObject = define({ + valid(editor: Editor) { + return editor.pc.selection.length > 0; + }, + execute(editor: Editor) { + let object = editor.pc.selection[0]; + editor.cmdManager.execute('delete-object', object); + }, +}); + +export const toggleShowAnnotation = define({ + valid(editor: Editor) { + return true; + }, + execute(editor: Editor) { + let config = editor.state.config; + config.showAnnotation = !config.showAnnotation; + editor.pc.render(); + }, +}); + +export const toggleShowLabel = define({ + valid(editor: Editor) { + return true; + }, + execute(editor: Editor) { + let config = editor.state.config; + config.showLabel = !config.showLabel; + editor.pc.render(); + }, +}); +export const toggleShowMeasure = define({ + valid(editor: Editor) { + return true; + }, + execute(editor: Editor) { + let groupTrack = editor.pc.groupTrack; + groupTrack.visible = !groupTrack.visible; + editor.pc.render(); + }, +}); +export const pickObject = define({ + valid(editor: Editor) { + return true; + }, + end(editor: Editor) { + let view = editor.viewManager.getMainView(); + let action = view.getAction('create-obj') as CreateAction; + action.end(); + editor.state.status = StatusType.Default; + }, + execute(editor: Editor) { + let view = editor.viewManager.getMainView(); + editor.state.status = StatusType.Pick; + + if (view) { + return new Promise(async (resolve) => { + let action = view.getAction('create-obj') as CreateAction; + this.action = action; + + action.start( + { type: 'points-1', trackLine: false }, + async (data: THREE.Vector2[]) => { + let obj = view.getObjectByCanvas(data[0]); + // editor.state.status = StatusType.Default; + resolve(obj); + }, + ); + }); + } + }, +}); +export const copyForward = define({ + valid(editor: Editor) { + return editor.state.isSeriesFrame; + }, + execute(editor: Editor) { + editor.dataManager.copyForward(); + }, +}); +export const copyBackWard = define({ + valid(editor: Editor) { + return editor.state.isSeriesFrame; + }, + execute(editor: Editor) { + editor.dataManager.copyBackWard(); + }, +}); + +export const resultExpandToggle = define({ + execute(editor: Editor) { + editor.dispatchEvent({ type: Event.RESULT_EXPAND_TOGGLE }); + }, +}); + +export const filter2DByTrack = define({ + execute(editor: Editor) { + let config = editor.state.config; + config.filter2DByTrack = !config.filter2DByTrack; + editor.pc.render(); + }, +}); diff --git a/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/index.ts b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/index.ts new file mode 100644 index 00000000..1041ccc9 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/index.ts @@ -0,0 +1,8 @@ +export * from './create'; +export * from './create2D'; +export * from './undo-redo'; +export * from './sideView'; +export * from './general'; +export * from './viewUI'; +export * from './viewAngle'; +export * from './frame'; diff --git a/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/sideView.ts b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/sideView.ts new file mode 100644 index 00000000..60eca1f0 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/sideView.ts @@ -0,0 +1,180 @@ +import { SideRenderView, ResizeTransAction, Box } from 'pc-render'; +import Editor from '../../../Editor'; +import { define } from '../define'; +import * as THREE from 'three'; + +let offset = 0.02; +export const translateXPlus = define({ + valid(editor: Editor) { + let object = editor.pc.selection.find((annotate) => annotate instanceof Box); + return !!object; + }, + execute(editor: Editor) { + let object = editor.pc.selection.find((annotate) => annotate instanceof Box) as Box; + let dir = new THREE.Vector3(offset, 0, 0); + toWorld(dir, object); + + dir.add(object.position); + + editor.cmdManager.execute('update-transform', { object, transform: { position: dir } }); + }, +}); + +export const translateXMinus = define({ + valid(editor: Editor) { + // let selection = editor.pc.selection; + // return !!(selection.length === 1 && selection[0] instanceof Box); + let object = editor.pc.selection.find((annotate) => annotate instanceof Box); + return !!object; + }, + execute(editor: Editor) { + let object = editor.pc.selection.find((annotate) => annotate instanceof Box) as Box; + let dir = new THREE.Vector3(-offset, 0, 0); + toWorld(dir, object); + + dir.add(object.position); + + editor.cmdManager.execute('update-transform', { object, transform: { position: dir } }); + }, +}); + +export const translateYPlus = define({ + valid(editor: Editor) { + // let selection = editor.pc.selection; + // return !!(selection.length === 1 && selection[0] instanceof Box); + let object = editor.pc.selection.find((annotate) => annotate instanceof Box); + return !!object; + }, + execute(editor: Editor) { + let object = editor.pc.selection.find((annotate) => annotate instanceof Box) as Box; + let dir = new THREE.Vector3(0, offset, 0); + toWorld(dir, object); + + dir.add(object.position); + + editor.cmdManager.execute('update-transform', { object, transform: { position: dir } }); + }, +}); + +export const translateYMinus = define({ + valid(editor: Editor) { + // let selection = editor.pc.selection; + // return !!(selection.length === 1 && selection[0] instanceof Box); + let object = editor.pc.selection.find((annotate) => annotate instanceof Box); + return !!object; + }, + execute(editor: Editor) { + let object = editor.pc.selection.find((annotate) => annotate instanceof Box) as Box; + let dir = new THREE.Vector3(0, -offset, 0); + toWorld(dir, object); + + dir.add(object.position); + + editor.cmdManager.execute('update-transform', { object, transform: { position: dir } }); + }, +}); + +export const translateZPlus = define({ + valid(editor: Editor) { + // let selection = editor.pc.selection; + // return !!(selection.length === 1 && selection[0] instanceof Box); + let object = editor.pc.selection.find((annotate) => annotate instanceof Box); + return !!object; + }, + execute(editor: Editor) { + let object = editor.pc.selection.find((annotate) => annotate instanceof Box) as Box; + let dir = new THREE.Vector3(0, 0, offset); + toWorld(dir, object); + + dir.add(object.position); + + editor.cmdManager.execute('update-transform', { object, transform: { position: dir } }); + }, +}); + +export const translateZMinus = define({ + valid(editor: Editor) { + // let selection = editor.pc.selection; + // return !!(selection.length === 1 && selection[0] instanceof Box); + let object = editor.pc.selection.find((annotate) => annotate instanceof Box); + return !!object; + }, + execute(editor: Editor) { + let object = editor.pc.selection.find((annotate) => annotate instanceof Box) as Box; + let dir = new THREE.Vector3(0, 0, -offset); + toWorld(dir, object); + + dir.add(object.position); + + editor.cmdManager.execute('update-transform', { object, transform: { position: dir } }); + }, +}); + +export const rotationZLeft = define({ + valid(editor: Editor) { + // let selection = editor.pc.selection; + // return !!(selection.length === 1 && selection[0] instanceof Box); + let object = editor.pc.selection.find((annotate) => annotate instanceof Box); + return !!object; + }, + execute(editor: Editor) { + let object = editor.pc.selection.find((annotate) => annotate instanceof Box) as Box; + let rotation = getRotationZ(offset, 1, object); + editor.cmdManager.execute('update-transform', { object, transform: { rotation } }); + }, +}); + +export const rotationZRight = define({ + valid(editor: Editor) { + // let selection = editor.pc.selection; + // return !!(selection.length === 1 && selection[0] instanceof Box); + let object = editor.pc.selection.find((annotate) => annotate instanceof Box); + return !!object; + }, + execute(editor: Editor) { + let object = editor.pc.selection.find((annotate) => annotate instanceof Box) as Box; + let rotation = getRotationZ(offset, -1, object); + editor.cmdManager.execute('update-transform', { object, transform: { rotation } }); + }, +}); + +export const rotationZRight90 = define({ + valid(editor: Editor) { + // let selection = editor.pc.selection; + // return !!(selection.length === 1 && selection[0] instanceof Box); + let object = editor.pc.selection.find((annotate) => annotate instanceof Box); + return !!object; + }, + execute(editor: Editor) { + let object = editor.pc.selection.find((annotate) => annotate instanceof Box) as Box; + let rotation = getRotationZ(Math.PI / 2, -1, object); + let scale = object.scale.clone(); + + let temp = scale.x; + scale.x = scale.y; + scale.y = temp; + + editor.cmdManager.execute('update-transform', { object, transform: { rotation, scale } }); + }, +}); + +let tempV3 = new THREE.Vector3(); +function toWorld(offset: THREE.Vector3, object: THREE.Object3D) { + // console.log('start',offset) + let center = tempV3.set(0, 0, 0).applyMatrix4(object.matrixWorld); + offset.applyMatrix4(object.matrixWorld).sub(center); + // console.log('end',offset) +} + +let tempQuat = new THREE.Quaternion(); +let starQuat = new THREE.Quaternion(); +let axisDir = new THREE.Vector3(0, 0, 1); +function getRotationZ(angle: number, dir: number, object: THREE.Object3D) { + starQuat.setFromEuler(object.rotation); + tempQuat.setFromAxisAngle(axisDir, dir * angle); + tempQuat.premultiply(starQuat); + + let rotation = new THREE.Euler(); + rotation.setFromQuaternion(tempQuat); + return rotation; +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/undo-redo.ts b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/undo-redo.ts new file mode 100644 index 00000000..d72adbdd --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/undo-redo.ts @@ -0,0 +1,20 @@ +import Editor from '../../../Editor'; +import { define } from '../define'; + +export const undo = define({ + valid(editor: Editor) { + return true; + }, + execute(editor: Editor) { + editor.cmdManager.undo(); + }, +}); + +export const redo = define({ + valid(editor: Editor) { + return true; + }, + execute(editor: Editor) { + editor.cmdManager.redo(); + }, +}); diff --git a/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/viewAngle.ts b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/viewAngle.ts new file mode 100644 index 00000000..9f7e0f7f --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/viewAngle.ts @@ -0,0 +1,68 @@ +// import { Box, MainRenderView, TransformControlsAction } from 'pc-render'; +import Editor from '../../../Editor'; +import { define } from '../define'; +import * as THREE from 'three'; +import { MainRenderView, ViewHelperAction, viewType } from 'pc-render'; +function view(editor: Editor, type: viewType) { + let view = editor.pc.renderViews.find((e) => e instanceof MainRenderView) as MainRenderView; + if (view) { + let action = view.getAction('view-helper') as ViewHelperAction; + if (action) { + return action.view(type); + } + } +} +export const toggleViewNegY = define({ + valid(editor: Editor) { + let state = editor.state; + return !state.config.showSingleImgView && state.modeConfig.actions['toggleViewNegY']; + }, + execute(editor: Editor) { + return view(editor, 'negY'); + }, +}); +export const toggleViewNegX = define({ + valid(editor: Editor) { + let state = editor.state; + return !state.config.showSingleImgView && state.modeConfig.actions['toggleViewNegX']; + }, + execute(editor: Editor) { + return view(editor, 'negX'); + }, +}); +export const toggleViewNegZ = define({ + valid(editor: Editor) { + let state = editor.state; + return !state.config.showSingleImgView && state.modeConfig.actions['toggleViewNegZ']; + }, + execute(editor: Editor) { + return view(editor, 'negZ'); + }, +}); +export const toggleViewPosY = define({ + valid(editor: Editor) { + let state = editor.state; + return !state.config.showSingleImgView && state.modeConfig.actions['toggleViewPosY']; + }, + execute(editor: Editor) { + return view(editor, 'posY'); + }, +}); +export const toggleViewPosX = define({ + valid(editor: Editor) { + let state = editor.state; + return !state.config.showSingleImgView && state.modeConfig.actions['toggleViewPosX']; + }, + execute(editor: Editor) { + return view(editor, 'posX'); + }, +}); +export const toggleViewPosZ = define({ + valid(editor: Editor) { + let state = editor.state; + return !state.config.showSingleImgView && state.modeConfig.actions['toggleViewPosZ']; + }, + execute(editor: Editor) { + return view(editor, 'posZ'); + }, +}); diff --git a/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/viewUI.ts b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/viewUI.ts new file mode 100644 index 00000000..b9536164 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/action/viewUI.ts @@ -0,0 +1,15 @@ +// import { Box, MainRenderView, TransformControlsAction } from 'pc-render'; +import Editor from '../../../Editor'; +import { define } from '../define'; +// import * as THREE from 'three'; + +export const toggleClassView = define({ + valid(editor: Editor) { + let selection = editor.pc.selection; + return selection.length > 0; + }, + execute(editor: Editor) { + let state = editor.state; + state.config.showClassView = !state.config.showClassView; + }, +}); diff --git a/frontend/text-tool/src/packages/pc-editor/common/ActionManager/define.ts b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/define.ts new file mode 100644 index 00000000..93e32bfc --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/define.ts @@ -0,0 +1,27 @@ +import type Editor from '../../Editor'; + +export interface IActionOption { + name?: string; + // clear?: (e: Editor) => void; + end?: (e: Editor) => void; + isContinue?: (e: Editor) => boolean; + valid?: (e: Editor) => boolean; + execute: (e: Editor, args: any) => Promise | void | Error; + [key: string]: any; +} + +export type IAction = Required; + +function noop() {} +const defaultOption: IAction = { + name: '', + valid: () => true, + execute: noop, + // clear: noop, + end: noop, + isContinue: () => false, +}; + +export function define(option: IActionOption): IAction { + return Object.assign({}, defaultOption, option); +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/ActionManager/index.ts b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/index.ts new file mode 100644 index 00000000..2c615c32 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/index.ts @@ -0,0 +1,138 @@ +import Editor from '../../Editor'; +import * as Actions from './action'; +import { IAction } from './type'; +import { StatusType } from '../../type'; +import { Box } from 'pc-render'; + +export type IActionName = keyof typeof Actions; + +Object.keys(Actions).forEach((name) => { + Actions[name as IActionName].name = name; +}); + +export let AllActions = Object.keys(Actions) as IActionName[]; + +interface IActionArgs { + createAnnotation: { object: Box }; + projectObject2D: { updateFlag?: boolean; createFlag?: boolean; selectFlag?: boolean }; + [key: string]: any; +} + +export default class ActionManager { + // static Actions = Actions; + editor: Editor; + actions: Record; + currentAction: IAction | null = null; + constructor(editor: Editor) { + this.editor = editor; + this.actions = { ...Actions }; + } + + registryAction(name: string, action: IAction) { + this.actions[name] = action; + } + + async execute(name: T | T[], args?: IActionArgs[T]): Promise { + let action = null; + if (Array.isArray(name)) { + action = this.getEnableAction(name); + } else { + action = this.actions[name] as IAction; + } + + if (!action) return; + + if (this.isBlocked()) { + console.log(`action ${name} blocked`); + return; + } + + let result; + if (action.valid(this.editor)) { + console.log('action start:', action.name); + + this.currentAction = action; + try { + result = await action.execute(this.editor, args); + } catch (e) { + console.error('action error:', name); + console.error(e); + } + this.currentAction = null; + action.end(this.editor); + console.log('action end:', name); + } + + return result; + } + + isBlocked() { + return this.currentAction || this.editor.state.status !== StatusType.Default; + } + + getEnableAction(names: IActionName[]) { + let config = this.editor.state.modeConfig; + for (let i = 0; i < names.length; i++) { + let name = names[i]; + let action = this.actions[name] as IAction; + if (action && config.actions[name] && action.valid(this.editor)) return action; + } + + return null; + } + + stopCurrentAction() { + if (!this.currentAction) return; + + this.currentAction.end(this.editor); + console.log(`stop action: ${this.currentAction.name}`); + + this.currentAction = null; + } + + handleEsc() { + if (this.currentAction) { + this.stopCurrentAction(); + } else if (this.editor.pc.selection.length > 0) { + // this.editor.cmdManager.execute('select-object'); + this.editor.selectObject(); + } + } + + handleTab() { + let createActions: IActionName[] = [ + 'create2DBox', + 'create2DRect', + 'createAnnotation', + 'createObjectWith3', + ]; + + let config = this.editor.state.config; + if (this.currentAction) { + let action = this.currentAction; + let actionName = action.name as IActionName; + + if (createActions.indexOf(actionName) < 0) return; + + this.stopCurrentAction(); + + switch (actionName) { + case 'create2DBox': + config.active2DBox = false; + case 'create2DRect': + config.activeRect = false; + case 'createAnnotation': + config.activeAnnotation = false; + case 'createObjectWith3': + config.active3DBox = false; + } + } + } + + isActionValid(name: IActionName) { + let action = this.actions[name] as IAction; + if (!action) return false; + + return action.valid(this.editor); + } +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/ActionManager/type.ts b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/type.ts new file mode 100644 index 00000000..71893b51 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/ActionManager/type.ts @@ -0,0 +1,2 @@ +export type { IActionName } from './index'; +export type { IAction } from './define'; diff --git a/frontend/text-tool/src/packages/pc-editor/common/BSError.ts b/frontend/text-tool/src/packages/pc-editor/common/BSError.ts new file mode 100644 index 00000000..2f8bb422 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/BSError.ts @@ -0,0 +1,10 @@ +export default class BSError { + code: string; + message: string; + oriError: any; + constructor(code?: string, message?: string, oriError?: any) { + this.code = code || ''; + this.message = message || ''; + this.oriError = oriError; + } +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/BusinessManager.ts b/frontend/text-tool/src/packages/pc-editor/common/BusinessManager.ts new file mode 100644 index 00000000..e37d65be --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/BusinessManager.ts @@ -0,0 +1,37 @@ +import Editor from '../Editor'; +import { IFrame, IObject, IFileConfig } from '../type'; + +export default class BusinessManager { + editor: Editor; + + constructor(editor: Editor) { + this.editor = editor; + } + + async loadFrameConfig(data: IFrame): Promise { + throw 'loadFrameConfig implement error'; + } + + async getFrameClassification( + frame: IFrame | IFrame[], + ): Promise>> { + throw 'getFrameClassification implement error'; + } + + async createAnnotation(data: { + type: 'object' | 'position'; + data: any; + tags: any[]; + msg: string; + }) { + throw 'createAnnotation implement error'; + } + + async getFrameObject(frame: IFrame | IFrame[]): Promise<{ + objectsMap: Record; + classificationMap: Record; + queryTime: string; + }> { + throw 'getFrameObject implement error'; + } +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/CmdManager/CmdBase.ts b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/CmdBase.ts new file mode 100644 index 00000000..ccc87789 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/CmdBase.ts @@ -0,0 +1,27 @@ +import Editor from '../../Editor'; +export default class CmdBase { + name: string = ''; + createTime: number = 0; + updateTime: number = 0; + editor: Editor; + data: T; + undoData: D | null = null; + constructor(editor: Editor, data: T) { + this.createTime = new Date().getTime(); + this.updateTime = this.createTime; + this.data = data; + this.editor = editor; + } + redo() { + throw new Error('need implement redo method'); + } + undo() { + throw new Error('need implement undo method'); + } + + canMerge(cmd: CmdBase) { + return false; + } + + merge(cmd: CmdBase) {} +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/CmdManager/CmdGroup.ts b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/CmdGroup.ts new file mode 100644 index 00000000..36cf2e03 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/CmdGroup.ts @@ -0,0 +1,27 @@ +import Editor from '../../Editor'; +import CmdBase from './CmdBase'; + +export default class CmdGroup extends CmdBase { + cmds: CmdBase[] = []; + constructor(editor: Editor) { + super(editor, null); + } + redo() { + try { + this.cmds.forEach((e) => { + e.redo(); + }); + } catch (error) { + console.error(error); + } + } + undo() { + try { + this.cmds.forEach((e) => { + e.undo(); + }); + } catch (error) { + console.error(error); + } + } +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/AddObject.ts b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/AddObject.ts new file mode 100644 index 00000000..7087a29d --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/AddObject.ts @@ -0,0 +1,46 @@ +import CmdBase from '../CmdBase'; +import * as THREE from 'three'; +import * as _ from 'lodash'; +import type { ICmdOption } from './index'; +import { AnnotateObject } from 'pc-render'; +import { IFrame } from '../../../type'; + +export interface IAddObjectItem { + objects: AnnotateObject | AnnotateObject[]; + frame?: IFrame; +} + +export type IAddObjectOption = IAddObjectItem[] | AnnotateObject | AnnotateObject[]; + +export default class AddObject3D extends CmdBase { + redo(): void { + let editor = this.editor; + let { frames, frameIndex } = this.editor.state; + let frame = editor.getCurrentFrame(); + + if (!Array.isArray(this.data) || !(this.data[0] as any).objects) { + let objects = Array.isArray(this.data) ? this.data : [this.data]; + let data: IAddObjectItem[] = [{ objects: objects as AnnotateObject[] }]; + this.data = data; + } + + let data = this.data as IAddObjectItem[]; + + data.forEach((data) => { + if (!data.frame) data.frame = frame; + editor.dataManager.addAnnotates(data.objects, data.frame, false); + }); + editor.dataManager.loadDataFromManager(); + } + undo(): void { + let editor = this.editor; + let { frames } = this.editor.state; + + let data = this.data as IAddObjectItem[]; + + data.forEach((data) => { + editor.dataManager.removeAnnotates(data.objects, data.frame, false); + }); + editor.dataManager.loadDataFromManager(); + } +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/DeleteObject.ts b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/DeleteObject.ts new file mode 100644 index 00000000..8351782f --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/DeleteObject.ts @@ -0,0 +1,51 @@ +import CmdBase from '../CmdBase'; +import * as THREE from 'three'; +import * as _ from 'lodash'; +import type { ICmdOption } from './index'; +import type { AnnotateObject } from 'pc-render'; +import { IFrame } from '../../../type'; + +export interface IDeleteObjectItem { + objects: AnnotateObject | AnnotateObject[]; + frame?: IFrame; +} + +export type IDeleteObjectOption = IDeleteObjectItem[] | AnnotateObject | AnnotateObject[]; + +export default class DeleteObject extends CmdBase< + ICmdOption['delete-object'], + ICmdOption['delete-object'] +> { + redo(): void { + let editor = this.editor; + let { frameIndex, frames } = this.editor.state; + // let selection = editor.pc.selection; + + if (!Array.isArray(this.data) || !(this.data[0] as any).objects) { + let objects = Array.isArray(this.data) ? this.data : [this.data]; + let data: IDeleteObjectItem[] = [{ objects: objects as AnnotateObject[] }]; + this.data = data; + } + + // this.undoData = this.data; + let data = this.data as IDeleteObjectItem[]; + + data.forEach((data) => { + if (!data.frame) data.frame = editor.getCurrentFrame(); + editor.dataManager.removeAnnotates(data.objects, data.frame, false); + }); + editor.dataManager.loadDataFromManager(); + + // editor.pc.removeObject(this.data); + } + undo(): void { + let editor = this.editor; + let { frames } = this.editor.state; + + let data = this.data as IDeleteObjectItem[]; + data.forEach((data) => { + editor.dataManager.addAnnotates(data.objects, data.frame, false); + }); + editor.dataManager.loadDataFromManager(); + } +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/SelectObject.ts b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/SelectObject.ts new file mode 100644 index 00000000..782b5edb --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/SelectObject.ts @@ -0,0 +1,24 @@ +import CmdBase from '../CmdBase'; +import * as THREE from 'three'; +import * as _ from 'lodash'; +import type { ICmdOption } from './index'; +import { AnnotateObject } from 'pc-render'; + +export default class SelectObject extends CmdBase { + redo(): void { + // let data = this.data; + let editor = this.editor; + + if (!this.undoData) { + this.undoData = editor.pc.selection; + } + + editor.selectObject(this.data); + } + undo(): void { + if (!this.undoData) return; + + let editor = this.editor; + editor.selectObject(this.undoData); + } +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/ToggleVisible.ts b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/ToggleVisible.ts new file mode 100644 index 00000000..80f10ffa --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/ToggleVisible.ts @@ -0,0 +1,40 @@ +import CmdBase from '../CmdBase'; +import * as THREE from 'three'; +import * as _ from 'lodash'; +import type { ICmdOption } from './index'; +import type { AnnotateObject } from 'pc-render'; +import { IFrame } from '../../../type'; + +export interface IToggleVisibleOption { + objects: AnnotateObject | AnnotateObject[]; + visible: boolean | boolean[]; +} + +export default class ToggleVisible extends CmdBase< + ICmdOption['toggle-visible'], + ICmdOption['toggle-visible'] +> { + redo(): void { + let editor = this.editor; + let { objects, visible } = this.data; + + if (!Array.isArray(objects)) objects = [objects]; + + if (this.undoData) { + let visibles = [] as boolean[]; + objects.forEach((object) => { + visibles.push(object.visible); + }); + this.undoData = { objects, visible: visibles }; + } + + editor.dataManager.setAnnotatesVisible(objects, visible); + } + undo(): void { + let editor = this.editor; + if (!this.undoData) return; + + let { objects, visible } = this.undoData; + editor.dataManager.setAnnotatesVisible(objects, visible); + } +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/Update2DBox.ts b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/Update2DBox.ts new file mode 100644 index 00000000..8b83d938 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/Update2DBox.ts @@ -0,0 +1,58 @@ +import CmdBase from '../CmdBase'; +import * as THREE from 'three'; +import type { ICmdOption } from './index'; + +export default class Update2DBox extends CmdBase< + ICmdOption['update-2d-box'], + Required +> { + redo(): void { + let { object, option } = this.data; + let editor = this.editor; + + if (!this.undoData) { + let positions1 = {} as Record; + object.positions1.forEach((pos, index) => { + positions1[index] = new THREE.Vector2().copy(pos); + }); + + let positions2 = {} as Record; + object.positions2.forEach((pos, index) => { + positions2[index] = new THREE.Vector2().copy(pos); + }); + + this.undoData = { positions1, positions2 }; + } + + editor.dataManager.setAnnotatesTransform(object, option); + } + undo(): void { + if (!this.undoData) return; + let editor = this.editor; + let { object } = this.data; + + editor.dataManager.setAnnotatesTransform(object, this.undoData); + } + canMerge(cmd: CmdBase): boolean { + let offsetTime = Math.abs(this.updateTime - cmd.updateTime); + return cmd instanceof Update2DBox && + this.data.object === cmd.data.object && + offsetTime < 500 + ? true + : false; + } + + merge(cmd: Update2DBox) { + let currentOption = this.data.option; + let option = cmd.data.option; + currentOption.positions1 = currentOption.positions1 || {}; + currentOption.positions2 = currentOption.positions2 || {}; + + option.positions1 = option.positions1 || {}; + option.positions2 = option.positions2 || {}; + + Object.assign(currentOption.positions1, option.positions1); + Object.assign(currentOption.positions2, option.positions2); + this.updateTime = new Date().getTime(); + } +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/Update2DRect.ts b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/Update2DRect.ts new file mode 100644 index 00000000..0a6b0ad3 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/Update2DRect.ts @@ -0,0 +1,41 @@ +import CmdBase from '../CmdBase'; +import * as THREE from 'three'; +import type { ICmdOption } from './index'; + +export default class Update2DRect extends CmdBase< + ICmdOption['update-2d-rect'], + Required +> { + redo(): void { + let { object, option } = this.data; + let editor = this.editor; + + if (!this.undoData) { + let center = new THREE.Vector2().copy(object.center); + let size = new THREE.Vector2().copy(object.size); + this.undoData = { center, size }; + } + + editor.dataManager.setAnnotatesTransform(object, option); + } + undo(): void { + if (!this.undoData) return; + let editor = this.editor; + let { object } = this.data; + + editor.dataManager.setAnnotatesTransform(object, this.undoData); + } + canMerge(cmd: CmdBase): boolean { + let offsetTime = Math.abs(this.updateTime - cmd.updateTime); + return cmd instanceof Update2DRect && + this.data.object === cmd.data.object && + offsetTime < 500 + ? true + : false; + } + + merge(cmd: Update2DRect) { + Object.assign(this.data.option, cmd.data.option); + this.updateTime = new Date().getTime(); + } +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/UpdateObjectUserData.ts b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/UpdateObjectUserData.ts new file mode 100644 index 00000000..a741c814 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/UpdateObjectUserData.ts @@ -0,0 +1,76 @@ +import CmdBase from '../CmdBase'; +import * as THREE from 'three'; +import * as _ from 'lodash'; +import type { ICmdOption } from './index'; +import { IObject, IFrame, IUserData } from '../../../type'; +import { ITransform, Box, AnnotateObject } from 'pc-render'; +import * as utils from '../../../utils'; + +export interface IUpdateObjectUserDataOption { + objects: AnnotateObject[] | AnnotateObject; + data: IUserData[] | IUserData; +} + +export default class UpdateObjectUserData extends CmdBase< + ICmdOption['update-object-user-data'], + ICmdOption['update-object-user-data'] +> { + redo(): void { + let editor = this.editor; + let { frames } = this.editor.state; + + let { data, objects } = this.data; + + if (!Array.isArray(objects)) objects = [objects]; + + if (!this.undoData) { + let undoData: IUpdateObjectUserDataOption = { + objects: objects, + data: [], + // transform: { objects: [], transforms: [] }, + }; + + let attrKeys = Object.keys(Array.isArray(data) ? data[0] : data); + objects.forEach((object, index) => { + // let newData = Array.isArray(data) ? data : data[index]; + let copeData = utils.pickAttrs(object.userData, attrKeys) as IUserData; + undoData.data.push(copeData); + }); + + this.undoData = undoData; + } + + editor.dataManager.setAnnotatesUserData(objects, data); + if (objects.length > 0) this.editor.updateObjectRenderInfo(objects); + } + undo(): void { + let editor = this.editor; + let { frames } = this.editor.state; + + if (!this.undoData) return; + + let { data, objects } = this.undoData; + if (!Array.isArray(objects)) objects = [objects]; + + editor.dataManager.setAnnotatesUserData(objects, data); + + if (objects.length > 0) this.editor.updateObjectRenderInfo(objects); + } + + canMerge(cmd: UpdateObjectUserData): boolean { + let data = this.data; + let offsetTime = Math.abs(this.updateTime - cmd.updateTime); + let valid = + cmd instanceof UpdateObjectUserData && + data.objects === data.objects && + !Array.isArray(data.data) && + offsetTime < 1000; + + return valid; + } + + merge(cmd: UpdateObjectUserData) { + Object.assign(this.data.data, cmd.data.data); + this.updateTime = new Date().getTime(); + } +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/UpdateTransform.ts b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/UpdateTransform.ts new file mode 100644 index 00000000..0b7db657 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/UpdateTransform.ts @@ -0,0 +1,42 @@ +import CmdBase from '../CmdBase'; +import * as THREE from 'three'; +import type { ICmdOption } from './index'; + +export default class UpdateTransform extends CmdBase< + ICmdOption['update-transform'], + Required +> { + redo(): void { + let { object, transform } = this.data; + let editor = this.editor; + + if (!this.undoData) { + let position = new THREE.Vector3().copy(object.position); + let scale = new THREE.Vector3().copy(object.scale); + let rotation = new THREE.Euler().copy(object.rotation); + this.undoData = { position, scale, rotation }; + } + + editor.dataManager.setAnnotatesTransform(object as any, transform); + } + undo(): void { + if (!this.undoData) return; + let editor = this.editor; + let { object } = this.data; + + editor.dataManager.setAnnotatesTransform(object as any, this.undoData); + } + canMerge(cmd: CmdBase): boolean { + let offsetTime = Math.abs(this.updateTime - cmd.updateTime); + return cmd instanceof UpdateTransform && + this.data.object === cmd.data.object && + offsetTime < 500 + ? true + : false; + } + + merge(cmd: UpdateTransform) { + Object.assign(this.data.transform, cmd.data.transform); + this.updateTime = new Date().getTime(); + } +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/index.ts b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/index.ts new file mode 100644 index 00000000..db039b01 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/cmd/index.ts @@ -0,0 +1,48 @@ +import * as THREE from 'three'; +import type { ITransform, AnnotateObject, Rect, Box2D } from 'pc-render'; +import AddObject, { IAddObjectOption } from './AddObject'; +import DeleteObject, { IDeleteObjectOption } from './DeleteObject'; +import UpdateTransform from './UpdateTransform'; +import Update2DRect from './Update2DRect'; +import Update2DBox from './Update2DBox'; +import SelectObject from './SelectObject'; +import UpdateObjectDataBatch, { IUpdateObjectUserDataOption } from './UpdateObjectUserData'; +import ToggleVisible, { IToggleVisibleOption } from './ToggleVisible'; + +export interface ICmdOption { + 'add-object': IAddObjectOption; + 'delete-object': IDeleteObjectOption; + 'select-object': AnnotateObject | AnnotateObject[] | undefined; + 'update-transform': { + object: THREE.Object3D; + transform: ITransform; + }; + 'update-2d-rect': { + object: Rect; + option: { center: THREE.Vector2; size?: THREE.Vector2 }; + }; + 'update-2d-box': { + object: Box2D; + option: { + positions1?: Record; + positions2?: Record; + }; + }; + 'update-object-user-data': IUpdateObjectUserDataOption; + 'toggle-visible': IToggleVisibleOption; +} + +type Name = keyof ICmdOption; + +const CMD: Record = { + 'add-object': AddObject, + 'select-object': SelectObject, + 'delete-object': DeleteObject, + 'update-transform': UpdateTransform, + 'update-2d-rect': Update2DRect, + 'update-2d-box': Update2DBox, + 'update-object-user-data': UpdateObjectDataBatch, + 'toggle-visible': ToggleVisible, +}; + +export default CMD; diff --git a/frontend/text-tool/src/packages/pc-editor/common/CmdManager/index.ts b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/index.ts new file mode 100644 index 00000000..e8730706 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/index.ts @@ -0,0 +1,110 @@ +import CmdBase from './CmdBase'; +import CMD from './cmd/index'; +import Editor from '../../Editor'; +import type { ICmdOption } from './cmd/index'; +import * as THREE from 'three'; +import Event from '../../config/event'; +import CmdGroup from './CmdGroup'; + +export type ICmdName = keyof typeof CMD; + +export default class CmdManager extends THREE.EventDispatcher { + static Cmd = CMD; + editor: Editor; + cmds: CmdBase[] = []; + index: number = -1; + max: number = 20; + private _group: CmdGroup | null = null; + constructor(editor: Editor) { + super(); + this.editor = editor; + } + + execute(name: T | CmdGroup, data?: ICmdOption[T]) { + let cmd = {} as CmdBase; + if (name instanceof CmdGroup) { + cmd = name; + } else { + let CmdCtr = CmdManager.Cmd[name]; + if (!CmdCtr) return; + cmd = new CmdCtr(this.editor, data as any); + cmd.name = name; + } + + if (this._group) { + this._group.cmds.push(cmd); + return; + } + + this.cmds = this.cmds.slice(0, this.index + 1); + + let last = this.cmds[this.index]; + if (last && last.canMerge(cmd)) { + last.merge(cmd); + last.redo(); + } else { + cmd.redo(); + this.cmds.push(cmd); + } + + if (this.cmds.length > this.max) { + this.cmds = this.cmds.slice(-this.max); + } + + this.index = this.cmds.length - 1; + this.dispatchEvent({ type: Event.EXECUTE, data: { cmd, last } }); + } + + undo() { + if (this.index < 0 || this.cmds.length === 0) return; + + let cmd = this.cmds[this.index]; + + cmd.undo(); + + this.index--; + this.dispatchEvent({ type: Event.UNDO, data: { cmd } }); + } + + redo() { + if (this.cmds.length === 0 || this.index >= this.cmds.length - 1) return; + + let cmd = this.cmds[this.index + 1]; + + cmd.redo(); + + this.index++; + this.dispatchEvent({ type: Event.REDO, data: { cmd } }); + } + + withGroup(groupFn: () => void) { + // handle nested withGroup + if (this._group) { + groupFn(); + return; + } + + let group = new CmdGroup(this.editor); + this._group = group; + + let errFlag = false; + try { + groupFn(); + } catch (error) { + console.log(error); + errFlag = true; + } + + this._group = null; + + if (errFlag) return; + + if (group.cmds.length > 0) this.execute(group); + } + + reset() { + this.cmds = []; + this.index = -1; + this.dispatchEvent({ type: Event.RESET }); + } +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/CmdManager/type.ts b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/type.ts new file mode 100644 index 00000000..82f07a7d --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/CmdManager/type.ts @@ -0,0 +1,2 @@ +export type { ICmdName } from './index'; +export type { ICmdOption } from './cmd/index'; diff --git a/frontend/text-tool/src/packages/pc-editor/common/ConfigManager.ts b/frontend/text-tool/src/packages/pc-editor/common/ConfigManager.ts new file mode 100644 index 00000000..f9903197 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/ConfigManager.ts @@ -0,0 +1,65 @@ +import Editor from '../Editor'; +import { getColorRangeByArray, filterPosition } from '../utils'; +import * as THREE from 'three'; +import { Points, PointsMaterial } from 'pc-render'; + +export default class ConfigManager { + editor: Editor; + + constructor(editor: Editor) { + this.editor = editor; + + this.initConfig(); + } + + initConfig() { + } + + updatePointConfig(ground: number, intensityRange?: [number, number]) { + let { config } = this.editor.state; + let points = this.editor.pc.groupPoints.children[0] as Points; + + if (!points.geometry.boundingBox) points.geometry.computeBoundingBox(); + let boundingBox = points.geometry.boundingBox as THREE.Box3; + let position = points.geometry.getAttribute('position') as THREE.BufferAttribute; + + let pointIntensity = config.pointIntensity; + let pointInfo = config.pointInfo; + pointInfo.hasIntensity = !!intensityRange; + if (intensityRange) { + pointInfo.intensityRange.set(intensityRange[0], intensityRange[1]); + config.pointIntensity = [ + Math.max(intensityRange[0], pointIntensity[0]), + Math.min(intensityRange[1], pointIntensity[1]), + ]; + } else { + pointInfo.intensityRange.set(0, 0); + } + pointInfo.count = position.count; + pointInfo.min.copy(boundingBox.min); + pointInfo.max.copy(boundingBox.max); + config.heightRange[0] = Math.max(config.heightRange[0], pointInfo.min.z); + config.heightRange[1] = Math.min(config.heightRange[1], pointInfo.max.z); + + pointInfo.vCount = filterPosition(position, config.heightRange).length; + + const _ground = config.pointHeight[0]; + if (_ground < pointInfo.min.z || _ground > pointInfo.max.z) { + config.pointHeight[0] = ground; + } + // config.pointHeight[0] = Math.max(config.pointHeight[0], pointInfo.min.z); + config.pointHeight[1] = Math.min(config.pointHeight[1], pointInfo.max.z); + // config.groundValue = ground; + this.editor.pc.ground.plane.constant = config.pointHeight[0]; + let material = points.material as PointsMaterial; + material.setOption({ hasIntensity: pointInfo.hasIntensity }); + material.setUniforms({ + // groundValue: ground, + colorRange: getColorRangeByArray(config.pointColors, config.pointHeight, [ + config.pointInfo.min.z, + config.pointInfo.max.z, + ]), + heightRange: new THREE.Vector2().fromArray(config.heightRange), + }); + } +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/DataManager.ts b/frontend/text-tool/src/packages/pc-editor/common/DataManager.ts new file mode 100644 index 00000000..df3fe9b4 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/DataManager.ts @@ -0,0 +1,458 @@ +import { ITextItem, IFrame } from '../type'; +import Editor from '../Editor'; +import { Event as EditorEvent } from 'pc-editor'; +// import * as api from '../api'; +import * as utils from '../utils'; +import { Const, ICmdName, IFilter, IUserData } from '../type'; +import Event from '../config/event'; +import * as THREE from 'three'; + +interface ITransform2DBox { + positions2?: Record; + positions1?: Record; +} + +interface ITransform2DRect { + center?: THREE.Vector2; + size?: THREE.Vector2; +} + +export type IAnnotateTransform = ITransform2DBox | ITransform2DRect; + +export default class DataManager { + editor: Editor; + sceneMap: Map = new Map(); + textMap: Map = new Map(); + + ////////////////////////////////////////////// + // object + dataMap: Map = new Map(); + hasMap: Map> = new Map(); + constructor(editor: Editor) { + this.editor = editor; + this.initEvent(); + } + + /** + * scene + */ + setSceneData(data: IFrame[]) { + this.clearSceneMap(); + data.forEach((e) => { + let sceneKey = String(e.sceneId || -1); + let arr = this.sceneMap.get(sceneKey); + if (!arr) { + arr = []; + this.sceneMap.set(sceneKey, arr); + } + arr.push(e); + }); + } + getFramesBySceneIndex(index: number) { + const arr = Array.from(this.sceneMap.values()); + return arr[index] || []; + } + getFramesBySceneId(id: string) { + return this.sceneMap.get(id + '') || []; + } + clearSceneMap() { + this.sceneMap.clear(); + } + /** + * 文本内容数据 + */ + setJSONData(data: ITextItem[]) { + this.clearTextMap(); + data.forEach((e) => { + this.textMap.set(e.id, e); + }); + + } + getTextById(id: string) { + this.textMap.get(id); + } + clearTextMap() { + this.textMap.clear(); + } + + + + + + + + + + ///////////////////////////////////////////////////// + ///////////////////////////////////////////////////// + + hasObject(uuid: string, frame?: IFrame): boolean { + frame = frame || this.editor.getCurrentFrame(); + let frameMap = this.hasMap.get(frame.id); + return !!frameMap && frameMap.has(uuid); + } + + setHasMap(uuid: string, object: any, frame?: IFrame) { + frame = frame || this.editor.getCurrentFrame(); + let frameMap = this.hasMap.get(frame.id); + if (!frameMap) frameMap = new Map(); + frameMap.set(uuid, object); + } + + removeHasMap(uuid: string, frame?: IFrame) { + frame = frame || this.editor.getCurrentFrame(); + let frameMap = this.hasMap.get(frame.id); + if (frameMap) frameMap.delete(uuid); + } + + addAnnotates( + objects: any, + frame?: IFrame, + reload: boolean = true, + ) { + } + + removeAnnotates( + objects: any, + frame?: IFrame, + reload: boolean = true, + ) { + } + + setAnnotatesVisible( + objects: any, + visible: boolean | boolean[], + frame?: IFrame, + ) { + } + + setAnnotatesUserData( + objects: any, + datas: IUserData | IUserData[], + frame?: IFrame, + ) { + } + + setAnnotatesTransform( + objects: any, + datas: IAnnotateTransform | IAnnotateTransform[], + frame?: IFrame, + ) { + } + + initEvent() {} + + clear() { + this.dataMap.clear(); + } + + onAnnotatesChange( + objects: any, + frame?: IFrame, + data?: { type?: 'userData' | 'transform' | 'visible'; [k: string]: any }, + ) { + frame = frame || this.editor.getCurrentFrame(); + frame.needSave = true; + + if (data?.type === 'transform') { + // ANNOTATE_TRANSFORM_CHANGE + this.editor.dispatchEvent({ + type: Event.ANNOTATE_TRANSFORM_CHANGE, + data: { ...data, frame, objects }, + }); + } else { + console.log('onAnnotatesChange', { ...data, frame }); + this.editor.dispatchEvent({ + type: Event.ANNOTATE_CHANGE, + data: { ...data, frame, objects }, + }); + } + } + + onAnnotatesAdd(objects: any[], frame?: IFrame) { + frame = frame || this.editor.getCurrentFrame(); + } + + onAnnotatesRemove(objects: any[], frame?: IFrame) { + } + + setFrameObject(frameId: string, objects: any[]) { + } + + getFrameObject(frameId: string) { + return this.dataMap.get(frameId); + } + + loadDataFromManager() { + let frame = this.editor.getCurrentFrame(); + + console.log('loadDataFromManager', this.editor.state.frameIndex); + + let objects = this.getFrameObject(frame.id) || []; + + // this.editor.pc.addObject(annotate3D); + // this.editor.pc.annotate2D = annotate2D; + // this.editor.pc.annotate3D.children = annotate3D; + // this.editor.dispatchEvent({ type: Event.ANNOTATE_LOAD }); + // this.editor.pc.render(); + // this.editor.updateIDCounter(); + } + + setFilterFromData() { + let { frameIndex, frames } = this.editor.state; + let { FILTER_ALL } = this.editor.state.config; + + // if (this.editor.playManager.playing) { + // this.editor.state.filterActive = [FILTER_ALL]; + // return; + // } + + let frame = this.editor.getCurrentFrame(); + let objects = this.getFrameObject(frame.id) || []; + let all: IFilter = { value: FILTER_ALL, label: FILTER_ALL, type: '' }; + let project: IFilter = { label: 'Ground Truth', options: [], type: 'project' }; + let model: IFilter = { label: 'Model Runs', options: [], type: 'model' }; + + let projectMap = {}; + let modelMap = {}; + + objects.forEach((object) => { + let userData = object.userData as Required; + if (userData.modelRun) { + let id = userData.modelRun || ''; + let label = userData.modelRunLabel || ''; + if (!modelMap[id]) { + let option = { value: id, label: label }; + model.options?.push(option); + modelMap[id] = option; + } + } else { + let name = userData.project || ''; + if (!projectMap[name]) { + let option = { value: name, label: name || 'No Project' }; + project.options?.push(option); + projectMap[name] = option; + } + } + }); + + let filters = [all] as IFilter[]; + + if ((project as any).options.length > 0) filters.push(project); + if ((model as any).options.length > 0) filters.push(model); + + this.editor.state.filters = filters; + if (this.editor.state.filterActive.length === 0) + this.editor.state.filterActive = [FILTER_ALL]; + + this.editor.needUpdateFilter = false; + } + + getActiveFilter() { + let { FILTER_ALL } = this.editor.state.config; + let { sourceFilters } = this.editor.state; + + let filterMap = { + all: false, + source: {}, + // project: {}, + // model: {}, + }; + sourceFilters.forEach((filter) => { + if (filter === FILTER_ALL) filterMap.all = true; + else { + filterMap.source[filter] = true; + } + }); + + return filterMap; + } + + getMaxId(frameId?: string) { + let { frameIndex, frames } = this.editor.state; + let curFrame = frames[frameIndex]; + + let objects = this.getFrameObject(frameId || curFrame.id) || []; + let maxId = 0; + objects.forEach((e) => { + if (!e.userData.trackName) return; + let id = parseInt(e.userData.trackName); + if (id > maxId) maxId = id; + }); + return maxId; + } + + updateFrameId(frameId?: string) { + let { frameIndex, frames } = this.editor.state; + let curFrame = frames[frameIndex]; + + frameId = frameId || curFrame.id; + let objects = this.getFrameObject(frameId) || []; + + let startId = this.getMaxId(frameId) + 1; + objects.forEach((e) => { + let userData = e.userData as IUserData; + userData.id = userData.id || THREE.MathUtils.generateUUID(); + + if (userData.trackId) return; + + userData.trackId = this.editor.createTrackId(); + userData.trackName = startId++ + ''; + }); + + // if (curFrame && frameId === curFrame.id) this.editor.idCount = startId; + } + + updateBackId(keyMap: Record>) { + Object.keys(keyMap).forEach((dataId) => { + let dataKeyMap = keyMap[dataId]; + let annotates = this.getFrameObject(dataId) || []; + annotates.forEach((annotate) => { + let frontId = annotate.uuid; + let backId = dataKeyMap[frontId]; + if (!backId) return; + (annotate.userData as IUserData).backId = backId; + // annotate.uuid = backId; + }); + }); + } + + async pollDataModelResult() {} + + // async runModelTrack( + // curId: string, + // toIds: string[], + // direction: 'BACKWARD' | 'FORWARD', + // targetObjects: any[], + // trackIdName: Record, + // onComplete?: () => void, + // ) {} + // copyForward() { + // return this.track({ + // direction: 'FORWARD', + // object: this.editor.pc.selection.length > 0 ? 'select' : 'all', + // method: 'copy', + // frameN: 1, + // }); + // } + // copyBackWard() { + // return this.track({ + // direction: 'BACKWARD', + // object: this.editor.pc.selection.length > 0 ? 'select' : 'all', + // method: 'copy', + // frameN: 1, + // }); + // } + // async track(option: { + // method: 'copy' | 'model'; + // object: 'select' | 'all'; + // direction: 'BACKWARD' | 'FORWARD'; + // frameN: number; + // }) { + // let editor = this.editor; + // let { frameIndex, frames } = editor.state; + // let curId = frames[frameIndex].id; + + // const getToDataId = function getToDataId() { + // let ids = [] as string[]; + // let forward = option.direction === 'FORWARD' ? 1 : -1; + // let frameN = option.frameN; + + // if (frameN > 0) + // for (let i = 1; i <= frameN; i++) { + // let frame = frames[frameIndex + forward * i]; + // if (frame) { + // ids.push(frame.id); + // } + // } + // return ids; + // }; + // const getObjects = function getObjects() { + // let dataId = frames[frameIndex].id; + // let objects = editor.dataManager.getFrameObject(dataId) || []; + + // if (option.object === 'select') { + // objects = editor.pc.selection; + // } + + // objects = objects.filter((object) => { + // return object instanceof Box && !object.userData.invisibleFlag; + // }); + + // return objects as Box[]; + // }; + // let ids = getToDataId(); + // if (ids.length === 0) { + // // editor.showMsg('warning', props.state.$$('warnEmptyTarget')); + // return; + // } + + // let objects = getObjects(); + // if (objects.length === 0) { + // editor.showMsg('warning', editor.lang('track-no-source')); + // return; + // } + + // if (option.method === 'copy') { + // utils.copyData(editor, curId, ids, objects); + // editor.showMsg('success', editor.lang('track-ok')); + // this.gotoNext(ids[0]); + // } else { + // await this.modelTrack(ids, objects, option.direction); + // } + // } + // gotoNext(dataId: string) { + // let { frames } = this.editor.state; + // let index = frames.findIndex((e) => e.id === dataId); + // // index = Math.max(0, Math.min(editor.state.frames.length-1, index)) + // if (index < 0) return; + // this.editor.loadFrame(index); + // this.editor.dispatchEvent({ type: EditorEvent.UPDATE_TIME_LINE }); + // } + // async modelTrack( + // toIds: string[], + // objects: AnnotateObject[], + // direction: 'BACKWARD' | 'FORWARD', + // ) { + // let editor = this.editor; + // let { frameIndex, frames } = editor.state; + // let dataInfo = frames[frameIndex]; + // let curId = dataInfo.id; + // // let direction = iState.trackDirection === 'backward' ? 'BACKWARD' : 'FORWARD'; + // // let dataIds = dataList.slice(1, 10).map((e) => +e.dataId); + + // // Partial + // let trackIdName = {} as Record; + // let targetObjects = [] as any[]; + // objects.forEach((object) => { + // if (object instanceof Box) { + // let userData = object.userData as IUserData; + // let { position, scale, rotation } = object; + // let center3D = new THREE.Vector3().set(position.x, position.y, position.z); + // let rotation3D = new THREE.Vector3().set(rotation.x, rotation.y, rotation.z); + // let size3D = new THREE.Vector3().set(scale.x, scale.y, scale.z); + + // if (!userData.trackId) { + // userData.trackId = editor.createTrackId(); + // } + + // trackIdName[userData.trackId] = userData.trackName || ''; + + // targetObjects.push({ + // uuid: object.uuid, + // trackingId: userData.trackId, + // objType: '3d', + // modelClass: userData.modelClass || null, + // confidence: userData.confidence || null, + // center3D, + // rotation3D, + // size3D, + // }); + // } + // }); + + // this.runModelTrack(curId, toIds, direction as any, targetObjects, trackIdName, () => { + // this.gotoNext(toIds[0]); + // }); + // } +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/DataResource.ts b/frontend/text-tool/src/packages/pc-editor/common/DataResource.ts new file mode 100644 index 00000000..d828cc8b --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/DataResource.ts @@ -0,0 +1,218 @@ +import { IDataResource, IFrame, IFileConfig, PointAttr } from '../type'; +import Editor from '../Editor'; +// import * as api from '../api'; +import * as utils from '../utils'; +import Event from '../config/event'; +import { IImgViewConfig } from 'pc-editor'; + +export type LoadMode = 'near_2' | 'all'; +export class ResourceLoader { + manual: boolean = false; + data: IFrame; + dataResource: DataResource; + promise: Promise = {} as Promise; + constructor(dataResource: DataResource, data: IFrame) { + this.data = data; + this.dataResource = dataResource; + this.handleProgress = this.handleProgress.bind(this); + } + remove() { + this.dataResource.loaders = this.dataResource.loaders.filter( + (e) => e.data.id !== this.data.id, + ); + + setTimeout(() => { + this.dataResource.load(); + }); + } + get() { + return this.promise; + } + load() { + let promise: Promise = new Promise(async (resolve, reject) => { + try { + let config = await this.dataResource.loadDataConfig(this.data); + this.data.loadState = 'loading'; + let jsonStr = await this.onLoadJSON(config.url); + let jsonObj = JSON.parse(jsonStr); + resolve(jsonObj); + } catch (e) { + console.log(`load resource: ${this.data.id} err`); + this.data.loadState = 'error'; + this.remove(); + reject(e); + } + }); + + this.promise = promise; + } + handleProgress(percent: number) { + this.onProgress(percent); + } + onProgress(percent: number) { + // console.log(percent); + } + async onLoadJSON(url: string) { + return await fetch(url) + .then((res) => res.text()) + .then((json) => { + return json; + }); + } +} + +export default class DataResource { + loadMax: number = 500; + loadMode: LoadMode = 'near_2'; + editor: Editor; + dataMap: Record = {}; + loaders: ResourceLoader[] = []; + constructor(editor: Editor) { + this.editor = editor; + } + + clear() { + this.dataMap = {}; + this.loaders = []; + } + + async loadDataConfig(data: IFrame) { + return await this.editor.businessManager.loadFrameConfig(data); + } + + async loadImage(viewConfigs: IImgViewConfig[]) { + let requests = [] as Promise[]; + + viewConfigs.forEach((config) => { + if (!config.imgObject) { + requests.push(createRequest(config)); + } + }); + + if (requests.length) { + await Promise.all(requests); + } + + if (viewConfigs.filter((e) => !e.imgObject).length > 0) throw 'load image error'; + + function createRequest(config: IImgViewConfig): Promise { + return new Promise((resolve, reject) => { + let img = document.createElement('img') as HTMLImageElement; + img.src = config.imgUrl; + img.onload = () => { + config.imgObject = img; + config.imgSize = [img.naturalWidth, img.naturalHeight]; + resolve(img); + }; + img.onerror = () => { + resolve(null); + }; + img.onabort = () => { + resolve(null); + }; + }); + } + } + + calculatePointInfo(data: Record) { + let position = data.position || []; + let intensity = data.intensity || []; + + let intensityRange = undefined; + let ground = 0; + if (position.length > 0) ground = utils.getPositionGround(position); + if (intensity.length > 0) { + let min = Infinity; + let max = -Infinity; + for (let i = 0; i < intensity.length; i++) { + min = Math.min(intensity[i], min); + max = Math.max(intensity[i], max); + intensityRange = [min, max] as [number, number]; + } + } + return { ground, intensityRange }; + } + + async loadPoints(pointsUrl: string, onProgress?: (percent: number) => void): Promise { + return new Promise((resolve, reject) => {}); + } + + setLoadMode(mode: LoadMode) { + this.loadMode = mode; + } + + load(fromIndex?: number) { + let { frameIndex } = this.editor.state; + if (this.loaders.length > 0) return; + + let loaderN = Object.keys(this.dataMap).filter((e) => this.dataMap[e]).length; + if (loaderN > this.loadMax) return; + + fromIndex = fromIndex || frameIndex; + let data = this.getNext(fromIndex < 0 ? 0 : fromIndex); + + if (!data) { + console.log('load complete'); + return; + } + + this.loadNext(data); + } + + getNext(fromIndex: number) { + let { frames } = this.editor.state; + + let hasLoader = {} as Record; + this.loaders.forEach((e) => { + hasLoader[e.data.id] = true; + }); + + let nextDataIndex = -1; + let maxWeight = -1; + + for (let i = 0; i < frames.length; i++) { + let data = frames[i]; + if (data.loadState !== '') continue; + + let weight = 100000 - i; + let isNear2 = Math.abs(i - fromIndex) <= 1; + weight = isNear2 ? Infinity : weight; + if (this.loadMode === 'all' || (this.loadMode === 'near_2' && isNear2)) { + if (weight > maxWeight) { + maxWeight = weight; + nextDataIndex = i; + } + } + } + + console.log('nextDataIndex', nextDataIndex); + + if (nextDataIndex < 0) return null; + else return frames[nextDataIndex]; + } + + getResource(data: IFrame) { + let resource = this.dataMap[data.id]; + if (resource) { + resource.time = Date.now(); + return resource; + } + return this.loadNext(data, true); + } + + setResource(data: IFrame, resource: IDataResource) { + this.dataMap[data.id] = resource; + } + + loadNext(data: IFrame, manual: boolean = false) { + let oldLoader = this.loaders.find((e) => e.data.id === data.id); + if (this.loaders.length > 0 && oldLoader) return oldLoader; + + let loader = new ResourceLoader(this, data); + loader.manual = manual; + this.loaders.push(loader); + loader.load(); + + return loader; + } +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/HotkeyManager/index.ts b/frontend/text-tool/src/packages/pc-editor/common/HotkeyManager/index.ts new file mode 100644 index 00000000..74b929fc --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/HotkeyManager/index.ts @@ -0,0 +1,78 @@ +import hotkeys from 'hotkeys-js'; +import Editor from '../../Editor'; +import hotkeyConfig from '../../config/hotkey'; +import type { IHotkeyConfig } from './type'; +import { IActionName } from '../ActionManager'; + +export default class HotkeyManager { + editor: Editor; + hotkeyConfigs: IHotkeyConfig[]; + constructor(editor: Editor) { + this.editor = editor; + this.hotkeyConfigs = [...hotkeyConfig]; + // this.initHotkey(); + } + + initHotkey() { + hotkeyConfig.forEach((config) => { + this.bindConfig(config); + }); + } + + registryHotkey(configs: IHotkeyConfig[]) { + this.hotkeyConfigs = [...this.hotkeyConfigs, ...configs]; + } + + setHotKeyFromAction(actions: Record) { + hotkeys.unbind(); + + let filterConfig = this.hotkeyConfigs.filter((e) => { + return Array.isArray(e.action) ? validActions(actions, e.action) : actions[e.action]; + }); + filterConfig.forEach((config) => { + this.bindConfig(config); + }); + + this.bindEsc(); + } + + bindEsc() { + hotkeys('esc', (event, handler) => { + event.preventDefault(); + console.log('esc', '--> '); + this.editor.actionManager.handleEsc(); + }); + } + + bindTab() { + hotkeys('tab', (event, handler) => { + event.preventDefault(); + console.log('tab', '--> '); + this.editor.actionManager.handleTab(); + }); + } + + bindConfig(config: IHotkeyConfig) { + hotkeys(config.key, (event, handler) => { + event.preventDefault(); + console.log(config.key, '--> action:', config.action); + + if (config.viewType) { + if (this.editor.activeView && this.editor.activeView instanceof config.viewType) { + this.editor.actionManager.execute(config.action); + } + } else { + this.editor.actionManager.execute(config.action); + } + }); + } +} + +function validActions(actionMap: Record, actions: IActionName[]) { + for (let i = 0; i < actions.length; i++) { + let action = actions[i]; + if (actionMap[action]) return true; + } + + return false; +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/HotkeyManager/type.ts b/frontend/text-tool/src/packages/pc-editor/common/HotkeyManager/type.ts new file mode 100644 index 00000000..103b0f2d --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/HotkeyManager/type.ts @@ -0,0 +1,7 @@ +import type { IActionName } from '../ActionManager/type'; + +export interface IHotkeyConfig { + key: string; + action: T | T[]; + viewType?: any; +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/LoadManager.ts b/frontend/text-tool/src/packages/pc-editor/common/LoadManager.ts new file mode 100644 index 00000000..4f8dc020 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/LoadManager.ts @@ -0,0 +1,215 @@ +import Editor from '../Editor'; +import * as utils from '../utils'; +import * as THREE from 'three'; +import { IFrame, IObject, IDataResource, IUserData, Const, ITextItem } from '../type'; +import { ResourceLoader } from './DataResource'; +import { AnnotateObject } from 'pc-render'; +import Event from '../config/event'; + +export default class LoadManager { + editor: Editor; + + constructor(editor: Editor) { + this.editor = editor; + } + + async loadFrame(index: number, showLoading: boolean = true, force: boolean = false) { + let { frameIndex, frames } = this.editor.state; + if (index === frameIndex && !force) return; + if (index > frames.length - 1 || index < 0) return; + + this.editor.state.frameIndex = index; + + this.editor.actionManager.stopCurrentAction(); + + showLoading && this.editor.showLoading(true); + try { + await this.loadResource(); + await this.loadObjectAndClassification(); + } catch (error: any) { + this.editor.handleErr(error); + } + + showLoading && this.editor.showLoading(false); + } + + async loadClassification() { + let { frameIndex, frames, classifications } = this.editor.state; + let frame = frames[frameIndex]; + + // console.log('loadClassification', this.playManger.playing); + + if ( + classifications.length > 0 && + (!frame.classifications || frame.classifications.length === 0) + ) { + try { + // let valueMap = await api.getDataClassification(frame.id); + let valueMap = await this.editor.businessManager.getFrameClassification(frame); + let copClassifications = utils.copyClassification( + classifications, + valueMap[frame.id] || {}, + ); + + frame.classifications = copClassifications; + } catch (error: any) { + this.editor.handleErr(error, this.editor.lang('load-classification-error')); + } + } + } + + async loadObjectAndClassification() { + let { frameIndex, frames, classifications } = this.editor.state; + let frame = frames[frameIndex]; + + let objects = this.editor.dataManager.getFrameObject(frame.id); + if (!objects) { + try { + // let data = await api.getDataObject(datInfo.dataId); + let data = await this.editor.businessManager.getFrameObject(frame); + // frame.queryTime = data.queryTime; + // this.setTrackData(data.objectsMap); + + frame.classifications = utils.copyClassification( + classifications, + data.classificationMap[frame.id] || {}, + ); + + // let objects = data.objectsMap[frame.id] || []; + // let annotates = utils.convertObject2Annotate(objects, this.editor); + // this.editor.dataManager.setFrameObject(frame.id, annotates); + // this.editor.dataManager.updateFrameId(frame.id); + } catch (error: any) { + this.editor.handleErr(error, this.editor.lang('load-object-error')); + } + } + // console.log(annotates); + + // this.editor.reset(); + this.editor.state.filterActive = []; + // this.editor.dataManager.setFilterFromData(); + this.editor.dataManager.loadDataFromManager(); + // this.editor.updateIDCounter(); + // this.editor.pc.addObject(annotates); + } + + async loadAllClassification() { + let { frames, classifications } = this.editor.state; + + if (frames.length === 0) return; + + try { + // let valueMap = await api.getDataClassification(ids); + let valueMap = await this.editor.businessManager.getFrameClassification(frames); + + frames.forEach((frame) => { + let newClassifications = utils.copyClassification( + classifications, + valueMap[frame.id] || {}, + ); + + frame.classifications = newClassifications; + }); + } catch (error: any) { + this.editor.handleErr(error, this.editor.lang('load-classification-error')); + } + } + + // SeriesFrame load + async loadAllObjects() { + let { frames } = this.editor.state; + + let filterFrames = frames.filter((e) => !this.editor.dataManager.getFrameObject(e.id)); + + if (filterFrames.length === 0) return; + + try { + let data = await this.editor.businessManager.getFrameObject(filterFrames); + // let data = await api.getDataObject(dataIds); + + // this.setTrackData(data.objectsMap); + + filterFrames.forEach((frame) => { + let objects = data.objectsMap[frame.id] || []; + frame.queryTime = data.queryTime; + + let annotates = utils.convertObject2Annotate(objects, this.editor); + annotates.forEach((obj) => { + let userData = obj.userData as IUserData; + if (!userData.id) userData.id = THREE.MathUtils.generateUUID(); + }); + + this.editor.dataManager.setFrameObject(frame.id, annotates); + // this.editor.dataManager.updateFrameId(frame.id); + }); + } catch (error: any) { + this.editor.handleErr(error, this.editor.lang('load-object-error')); + } + } + + // setTrackData(objectsMap: Record) { + // // update trackId + // Object.keys(objectsMap).forEach((frameId) => { + // let objects = objectsMap[frameId] || []; + // objects.forEach((obj) => { + // if (!obj.trackId) obj.trackId = this.editor.createTrackId(); + // }); + // }); + + // let trackInfo = utils.getTrackFromObject(objectsMap); + // let objects = Object.keys(trackInfo.globalTrack).map((id) => trackInfo.globalTrack[id]); + // // update editor Id + // let maxId = getMaxId(objects); + // let startId = maxId + 1; + // objects.forEach((e) => { + // if (!e.trackName) e.trackName = startId++ + ''; + // }); + // this.editor.idCount = startId; + + // Object.keys(trackInfo.globalTrack).forEach((trackId) => { + // this.editor.trackManager.addTrackObject(trackId, trackInfo.globalTrack[trackId]); + // }); + + // function getMaxId(objects: Partial[]) { + // let maxId = 0; + // objects.forEach((e) => { + // if (!e.trackName) return; + // let id = parseInt(e.trackName); + // if (id > maxId) maxId = id; + // }); + // return maxId; + // } + // } + + async loadResource() { + let { frames, frameIndex } = this.editor.state; + let frame = frames[frameIndex]; + + let resource = this.editor.dataResource.getResource(frame); + if (resource instanceof ResourceLoader) { + console.log('load Resource'); + resource.onProgress = (ratio: number) => { + let percent = (ratio * 100).toFixed(2); + this.editor.showLoading({ + type: 'loading', + content: `${this.editor.lang('load-point')}${percent}%`, + }); + }; + return resource + .get() + .then((data) => { + this.setResource(data as any); + }) + .catch((e) => { + this.editor.handleErr(e, this.editor.lang('load-resource-error')); + }); + } else { + this.setResource(resource as any); + } + } + + setResource(data: ITextItem[]) { + console.log('=======setResource:', data); + this.editor.dataManager.setJSONData(data); + } +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/ModelManager.ts b/frontend/text-tool/src/packages/pc-editor/common/ModelManager.ts new file mode 100644 index 00000000..2295ad8d --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/ModelManager.ts @@ -0,0 +1,69 @@ +import { IObject, IFrame, IModelResult } from '../type'; +import { AnnotateObject, Box } from 'pc-render'; +import Editor from '../Editor'; +// import * as api from '../api'; +import * as utils from '../utils'; +import { Const, ICmdName, IFilter, IUserData } from '../type'; +import { utils as baseUtils } from 'pc-editor'; + +export default class ModelManager { + editor: Editor; + modelMap: Map = new Map(); + constructor(editor: Editor) { + this.editor = editor; + } + + // model + getModelResult(frameId: string) { + return this.modelMap.get(frameId); + } + clearModelResult(frameId: string) { + this.modelMap.delete(frameId); + } + + addModelData() { + let { frameIndex, frames } = this.editor.state; + let frame = this.editor.getCurrentFrame(); + let objects = this.getModelResult(frame.id) || []; + + if (objects.length === 0) return; + + // let oldAnnotate = this.dataManager.getDataObject(dataInfo.dataId); + let annotates = utils.convertObject2Annotate(objects, this.editor); + + let newTracks = [] as Partial[]; + + annotates.forEach((object) => { + let userData = object.userData as IUserData; + // userData.resultStatus = Const.Predicted; + + utils.setIdInfo(this.editor, userData); + + // let trackId = userData.trackId as string; + // let trackName = userData.trackName as string; + // let resultType = userData.resultType; + + // if (!this.editor.trackManager.hasTrackObject(trackId)) { + // newTracks.push({ trackId, trackName, resultType, classType: '' }); + // } + }); + + this.editor.needUpdateFilter = true; + this.editor.cmdManager.execute('add-object', annotates); + // this.editor.cmdManager.withGroup(() => { + // this.editor.cmdManager.execute('add-track', newTracks); + // this.editor.cmdManager.execute('add-object', annotates); + // }); + + // this.updateDataId(); + // this.dataManager.setDataObject(dataInfo.dataId, [...oldAnnotate, ...annotates]); + + frame.model = undefined; + + this.editor.frameChange(frame); + this.clearModelResult(frame.id); + } + + addModelTrackData(objectsMap: Record) { + } +} diff --git a/frontend/text-tool/src/packages/pc-editor/common/TxtDataManager.ts b/frontend/text-tool/src/packages/pc-editor/common/TxtDataManager.ts new file mode 100644 index 00000000..e69de29b diff --git a/frontend/text-tool/src/packages/pc-editor/common/ViewManager.ts b/frontend/text-tool/src/packages/pc-editor/common/ViewManager.ts new file mode 100644 index 00000000..f6398d0e --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/common/ViewManager.ts @@ -0,0 +1,86 @@ +import Editor from '../Editor'; +import * as THREE from 'three'; +import { OPType, IImgViewConfig } from '../type'; +import Stats from 'three/examples/jsm/libs/stats.module.js'; +import Event from '../config/event'; + +const trackGeometry = new THREE.RingGeometry(0.998, 1.002, 60); +const trackMaterial = new THREE.MeshBasicMaterial({ + color: 0xffff00, + side: THREE.DoubleSide, + depthTest: false, +}); + +export default class ViewManager { + initResize: boolean = false; + editor: Editor; + + constructor(editor: Editor) { + this.editor = editor; + this.handleWindowResize(); + + if (import.meta.env.DEV) { + this.initStats(); + } + } + // Stats + initStats() { + let stats = Stats(); + stats.dom.style.bottom = '300px'; + stats.dom.style.left = '10px'; + stats.dom.style.top = 'auto'; + document.body.appendChild(stats.dom); + + let frame = () => { + stats.update(); + requestAnimationFrame(frame); + }; + frame(); + } + + handleWindowResize() { + } + + updateViewAction(view: any) { + } + + updateViewStatus() { + } + + getMainView() { + } + + setImgViews(configs: IImgViewConfig[]) { + } + + // view + showSingleImgView(index: number) { + } + + showClassView(trackIds?: string | string[]) { + this.editor.state.config.showClassView = true; + if (trackIds) + this.editor.dispatchEvent({ type: Event.SHOW_CLASS_INFO, data: { id: trackIds } }); + } + + showImgView() { + } + + // track + addTrackCircle( + size: number = 100, + option: { name?: string; id?: number } = { name: '' }, + position: THREE.Vector3 = new THREE.Vector3(), + ) { + } + + updateTrackCircle(id: number, size: number) { + } + delTrackCircle(id: number) { + } + clearTracks() { + } + + updateBackgroundColor(color: string) { + } +} diff --git a/frontend/text-tool/src/packages/pc-editor/config/code.ts b/frontend/text-tool/src/packages/pc-editor/config/code.ts new file mode 100644 index 00000000..95e5d769 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/config/code.ts @@ -0,0 +1,6 @@ +const Code = { + NETWORK_ERROR: 'NETWORK_ERROR', + LOGIN_INVALID: 'LOGIN_INVALID', +}; + +export default Code; diff --git a/frontend/text-tool/src/packages/pc-editor/config/event.ts b/frontend/text-tool/src/packages/pc-editor/config/event.ts new file mode 100644 index 00000000..95d36a55 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/config/event.ts @@ -0,0 +1,43 @@ +const Event = { + UNDO: 'undo', + REDO: 'redo', + RESET: 'reset', + EXECUTE: 'execute', + SHOW_CLASS_INFO: 'show_class_info', + POINTS_CHANGE: 'points_change', + + FLOW_ACTION: 'flow_action', + UPDATE_RESULT_LIST: 'update_result_list', + UPDATE_CLASS_EDIT: 'update_class_edit', + // CLEAR_MERGE_SPLIT: 'clear_merge_split', + // PLAY_FRAME_CHANGE: 'play_frame_change', + // PLAY_STOP: 'play_stop', + // PLAY_START: 'play_start', + // PRE_MERGE_ACTION: 'pre_merge_action', + // PRE_SPLIT_ACTION: 'pre_split_action', + // UPDATE_TIME_LINE: 'update_time_line', + + FRAME_CHANGE: 'frame_change', + + ANNOTATE_TRANSFORM_CHANGE: 'annotate_transform_change', + ANNOTATE_CHANGE: 'annotate_change', + ANNOTATE_ADD: 'annotate_add', + ANNOTATE_REMOVE: 'annotate_remove', + ANNOTATE_LOAD: 'annotate_load', + ANNOTATE_SELECT: 'annotate_select', + // CURRENT_TRACK_CHANGE: 'current_track_change', + // TRACK_OBJECT_CHANGE: 'track_object_change', + + RESOURCE_LOAD_LOADING: 'resource_load_loading', + RESOURCE_LOAD_COMPLETE: 'resource_load_complete', + RESOURCE_LOAD_ERROR: 'resource_load_error', + + CHECK_UPDATE_FRAME_OBJECT: 'check_update_frame_object', + CHECK_UPDATE_VIEW: 'check_update_view', + CHECK_UPDATE_INFO: 'check_update_info', + + RESULT_EXPAND_TOGGLE: 'result_expand_toggle', + RESIZE: 'resize', +}; + +export default Event; diff --git a/frontend/text-tool/src/packages/pc-editor/config/hotkey.ts b/frontend/text-tool/src/packages/pc-editor/config/hotkey.ts new file mode 100644 index 00000000..3717b9d5 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/config/hotkey.ts @@ -0,0 +1,49 @@ +import { IHotkeyConfig } from '../type'; +import UAParser from 'ua-parser-js'; + +const parser = new UAParser(); +let osInfo = parser.getResult(); + +let osName = (osInfo.os.name || '').toLowerCase(); +let isMac = osName.indexOf('mac') >= 0; + +const hotkeyConfig: IHotkeyConfig[] = [ + { key: 'f', action: 'createObjectWith3' }, + // mac or window + { key: isMac ? 'backspace' : 'del', action: 'deleteObject' }, + { key: isMac ? '⌘+z' : 'ctrl+z', action: 'undo' }, + { key: isMac ? '⌘+shift+z' : 'ctrl+shift+z', action: 'redo' }, + + // side view + { key: 'e', action: 'translateYMinus' }, + { key: 'q', action: 'translateYPlus' }, + { key: 'a', action: 'translateXMinus' }, + { key: 'd', action: 'translateXPlus' }, + { key: 'w', action: 'translateZPlus' }, + { key: 's', action: 'translateZMinus' }, + { key: 'z', action: 'rotationZLeft' }, + { key: 'x', action: 'rotationZRight' }, + { key: 'c', action: 'rotationZRight90' }, + // + { key: 'g', action: 'toggleTranslate' }, + { key: 'y', action: 'focusObject' }, + // view + { key: 't', action: 'toggleClassView' }, + { key: 'm', action: 'toggleShowLabel' }, + { key: 'n', action: 'toggleShowMeasure' }, + { key: 'shift+h', action: 'toggleShowAnnotation' }, + { key: 'h', action: 'resultExpandToggle' }, + { key: 'b', action: 'filter2DByTrack' }, + + { key: '1', action: 'toggleViewPosZ' }, + { key: '2', action: 'toggleViewPosX' }, + { key: '3', action: 'toggleViewPosY' }, + { key: '4', action: 'toggleViewNegX' }, + { key: '5', action: 'toggleViewNegY' }, + { key: '6', action: 'toggleViewNegZ' }, + // + { key: 'right', action: 'nextFrame' }, + { key: 'left', action: 'preFrame' }, +]; + +export default hotkeyConfig; diff --git a/frontend/text-tool/src/packages/pc-editor/config/mode.ts b/frontend/text-tool/src/packages/pc-editor/config/mode.ts new file mode 100644 index 00000000..b8569cef --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/config/mode.ts @@ -0,0 +1,62 @@ +import { IModeConfig, OPType } from './type'; +import type { IActionName } from '../common/ActionManager/type'; +import { AllActions } from '../common/ActionManager'; +import * as _ from 'lodash'; + +function toMap(arr: T[]) { + let map = {} as Record; + arr.forEach((e) => (map[e] = true)); + return map; +} + +export const UIType = { + // ****** left tool********** + create3dBox: 'create3dBox', + create2dRect: 'create2dRect', + create2dBox: 'create2dBox', + translate: 'translate', + annotate: 'annotate', + project: 'project', + reProject: 'reProject', + track: 'track', + filter2D: 'filter2D', + setting: 'setting', + info: 'info', + + // *******side view********** + sideViewTool: 'sideViewTool', + + rumModel: 'rumModel', + flowSave: 'flowSave', +}; + +export type IUIType = keyof typeof UIType; + +let allUI = Object.keys(UIType) as IUIType[]; + +// test mode +const all: IModeConfig = { + name: 'all', + op: OPType.EXECUTE, + ui: toMap(allUI), + actions: toMap(AllActions), +}; + +// test mode +const empty: IModeConfig = { + name: 'empty', + op: OPType.VIEW, + ui: toMap([] as IUIType[]), + actions: toMap([] as IActionName[]), +}; + +let modes = { + empty, + all, +}; + +export type IModeType = keyof typeof modes; + +export const ModeKeys = Object.keys(modes).filter((e) => e !== 'all') as IModeType[]; + +export default modes; diff --git a/frontend/text-tool/src/packages/pc-editor/config/type.ts b/frontend/text-tool/src/packages/pc-editor/config/type.ts new file mode 100644 index 00000000..f4161294 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/config/type.ts @@ -0,0 +1,17 @@ +import type { IActionName } from '../common/ActionManager/type'; +import type { IModeType, IUIType } from './mode'; + +export { IModeType, IUIType }; + +export enum OPType { + EXECUTE = 'execute', + VERIFY = 'verify', + VIEW = 'view', +} + +export interface IModeConfig { + name: string; + op: OPType; + ui: Record; + actions: Record; +} diff --git a/frontend/text-tool/src/packages/pc-editor/index.ts b/frontend/text-tool/src/packages/pc-editor/index.ts new file mode 100644 index 00000000..9541f154 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/index.ts @@ -0,0 +1,36 @@ +import Editor from './Editor'; +import BSError from './common/BSError'; + +import CmdManager from './common/CmdManager'; +import HotkeyManager from './common/HotkeyManager'; +import ActionManager from './common/ActionManager'; +import BusinessManager from './common/BusinessManager'; +import DataManager from './common/DataManager'; +import LoadManager from './common/LoadManager'; + +import Event from './config/event'; +import { UIType } from './config/mode'; +import * as utils from './utils'; + +import modes, { ModeKeys } from './config/mode'; +import { AllActions } from './common/ActionManager'; +import { define as defineAction } from './common/ActionManager/define'; + +export { + Editor, + CmdManager, + LoadManager, + HotkeyManager, + ActionManager, + BusinessManager, + DataManager, + BSError, + Event, + ModeKeys, + modes, + AllActions, + UIType, + utils, + defineAction, +}; +export * from './type'; diff --git a/frontend/text-tool/src/packages/pc-editor/lang/en.ts b/frontend/text-tool/src/packages/pc-editor/lang/en.ts new file mode 100644 index 00000000..fc06cb97 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/lang/en.ts @@ -0,0 +1,42 @@ +const en = { + // load + 'load-resource-error': 'Load Resource Error', + 'load-object-error': 'Load Object Error', + 'load-classification-error': 'Load Classification Error', + 'load-class-error': 'Load Class Error', + 'load-model-error': 'Load Models Error', + 'load-dataset-classification-error': 'Load DataSet Classification Error', + 'load-record-error': 'Load Record Error', + 'load-frame-series-error': 'Load FrameSeries Data Error', + 'invalid-query': 'Invalid Query', + 'load-error': 'Load Error', + + // model + 'load-track': 'Tracking....', + 'track-no-data': "No Tracking object found, please check your objects' location and direction", + 'track-no-source': 'No Tracking Objects', + 'track-error': 'Track Error', + 'track-ok': 'Track Success', + + // info + 'load-point': 'Loading....', + 'save-ok': 'Save Success', + 'save-error': 'Save Error', + 'model-run-error': 'Model Run Error', + 'model-run-no-data': 'No Model Results', + 'no-point-data': 'No PointCloud Data', + 'play-error': 'Play Error', + 'unknown-error': 'Error', + 'network-error': 'Network Error', + 'login-invalid': 'Login Invalid', + 'not-login': 'Not logged in', + 'retry':'retry', + + // msg + 'msg-not-save': "You didn't save changes?", + 'create-rect-valid': "Don't beyond the picture", +}; + +export type ILocale = typeof en; + +export { en }; diff --git a/frontend/text-tool/src/packages/pc-editor/lang/index.ts b/frontend/text-tool/src/packages/pc-editor/lang/index.ts new file mode 100644 index 00000000..74ea3d05 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/lang/index.ts @@ -0,0 +1,2 @@ +export * from './zh'; +export * from './en'; diff --git a/frontend/text-tool/src/packages/pc-editor/lang/type.ts b/frontend/text-tool/src/packages/pc-editor/lang/type.ts new file mode 100644 index 00000000..af324dfb --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/lang/type.ts @@ -0,0 +1 @@ +export type { ILocale } from './en'; diff --git a/frontend/text-tool/src/packages/pc-editor/lang/zh.ts b/frontend/text-tool/src/packages/pc-editor/lang/zh.ts new file mode 100644 index 00000000..0133078d --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/lang/zh.ts @@ -0,0 +1,41 @@ +import { ILocale } from './type'; + +const zh: ILocale = { + 'load-resource-error': '加载资源失败', + 'load-object-error': '加载结果失败', + 'load-classification-error': '加载分类信息失败', + 'load-class-error': '加载标签失败', + 'load-model-error': '加载模型失败', + 'load-dataset-classification-error': '加载数据集分类失败', + 'load-record-error': '加载标注信息失败', + 'load-frame-series-error': '加载连续帧数据失败', + 'invalid-query': '参数不合法', + 'load-error': '加载失败', + + // model + 'load-track': '跟踪中....', + 'track-no-data': '无追踪结果,请检查你的结果信息和追踪方向是否正确', + 'track-error': '追踪错误', + 'track-no-source': '无追踪对象', + 'track-ok': '追踪成功', + + // info + 'load-point': '加载点云....', + 'save-ok': '保存成功', + 'save-error': '保存失败', + 'model-run-error': '模型运行异常', + 'model-run-no-data': '无模型结果', + 'no-point-data': '无点云数据', + 'play-error': '播放异常', + 'unknown-error': '异常错误', + 'network-error': '网络错误', + 'login-invalid': '登录过期', + 'not-login': '未登录', + 'retry':'重试', + + // msg + 'msg-not-save': '是否保存变更?', + 'create-rect-valid': '不允许标注到图片外', +}; + +export { zh }; diff --git a/frontend/text-tool/src/packages/pc-editor/lib/report.ts b/frontend/text-tool/src/packages/pc-editor/lib/report.ts new file mode 100644 index 00000000..e69de29b diff --git a/frontend/text-tool/src/packages/pc-editor/state.ts b/frontend/text-tool/src/packages/pc-editor/state.ts new file mode 100644 index 00000000..ad7b103d --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/state.ts @@ -0,0 +1,158 @@ +import * as THREE from 'three'; +import { IModeType, IModeConfig } from './config/type'; +import Modes from './config/mode'; + +import { + IConfig, + IImgViewConfig, + StatusType, + IClassType, + IAnnotationInfo, + // IUserInfo, + IFrame, + IFilter, + IClassification, + IAnnotationTag, + IModelConfig, + IAnnotationItem, + LangType, + IModelClass, + IModel, + IResultSource, + ICheckConfig, + IUserData, +} from './type'; + +const withoutTaskId = '-1'; +export interface IState { + lang: LangType; + // pointUrl: string; + + frames: IFrame[]; + frameIndex: number; + + filters: IFilter[]; + filterActive: string[]; + + classifications: IClassification[]; + // isSeriesFrame: boolean; + models: IModel[]; + modelConfig: IModelConfig; + recentClass: IClassType[]; + // user: IUserInfo; + + config: IConfig; + imgViews: IImgViewConfig[]; + modeConfig: IModeConfig; + status: StatusType; + activeSourceData: string; + sources: IResultSource[]; + sourceFilters: string[]; + classTypes: IClassType[]; +} + +export function getDefaultState(): IState { + const defaultState: IState = { + lang: 'en', + // pointUrl: '', + // user: { + // id: 'test-123123', + // }, + filters: [], + filterActive: [], + frames: [], + frameIndex: -1, + + classifications: [], + // isSeriesFrame: false, + models: [], + modelConfig: { + confidence: [0.5, 1], + predict: true, + classes: {} as { [key: string]: IModelClass[] }, + model: '', + loading: false, + start: 0, + duration: 0, + }, + // annotations: [], + recentClass: [], + + config: getDefaultConfig(), + imgViews: [], + // mode: 'empty', + modeConfig: Modes.empty, + status: StatusType.Default, + classTypes: [], + activeSourceData: withoutTaskId, + sources: [], + sourceFilters: [], + }; + + return defaultState; +} + +function getDefaultConfig(): IConfig { + return { + imgViewPrefix: 'image-2d-small', + singleViewPrefix: 'image-2d-max', + // + showClassView: false, + showImgView: true, + showSingleImgView: false, + showSideView: true, + showOperationView: true, + // showCheckView: false, + // showCheckClassView: false, + // showCheckViewImgMax: false, + showLabel: false, + // showAnnotation: true, + showAttr: false, + enableShowAttr: false, + // + filter2DByTrack: false, + singleImgViewIndex: 0, + imgRegionIndex: -1, + // + activeRect: false, + active2DBox: false, + active3DBox: false, + activeAnnotation: false, + activeTranslate: false, + activeTrack: false, + // project + projectPoint4: true, + projectPoint8: true, + projectMap3d: true, + // config + pointSize: 0.1, + heightRange: [-10000, 10000], + groundEnable: true, + // render + pointColorMode: 'height', + pointIntensity: [0, 255], + pointGround: -1.5, + pointColors: ['#141ff0', '#Fab942'], + pointHeight: [-10000, 10000], + pointInfo: { + count: 0, + hasIntensity: false, + intensityRange: new THREE.Vector2(), + min: new THREE.Vector3(), + max: new THREE.Vector3(), + vCount: 0, + }, + renderRect: true, + // renderProjectRect: true, + renderBox: true, + renderProjectBox: true, + renderProjectPoint: false, + // + FILTER_ALL: 'All', + aspectRatio: 1.78, + maxViewHeight: '100%', + maxViewWidth: '100%', + limitRect2Image: true, + withoutTaskId: withoutTaskId, + }; +} diff --git a/frontend/text-tool/src/packages/pc-editor/type.ts b/frontend/text-tool/src/packages/pc-editor/type.ts new file mode 100644 index 00000000..7c8b29e2 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/type.ts @@ -0,0 +1,432 @@ +import type { Vector2, Vector3 } from 'three'; +import { Vector2Of4, AnnotateType } from 'pc-render'; + +export * from './common/ActionManager/type'; +export * from './common/CmdManager/type'; +export * from './common/HotkeyManager/type'; +export * from './config/type'; + +export type { IState } from './state'; +export * from './uitype'; + +// export type { IMergeCodeData, IMergeStatus } from './common/TrackManager'; + +export enum AttrType { + RADIO = 'RADIO', + MULTI_SELECTION = 'MULTI_SELECTION', + DROPDOWN = 'DROPDOWN', + TEXT = 'TEXT', +} + +export enum Const { + Fixed = 'Fixed', + Dynamic = 'Dynamic', + Frozen = 'Frozen', + Standard = 'Standard', + True_Value = 'True_value', + Predicted = 'Predicted', + Copied = 'Copied', +} + +export type ResultType = Const.Dynamic | Const.Fixed; +export type ResultStatus = Const.True_Value | Const.Predicted | Const.Copied; + +export type LangType = 'zh' | 'en'; + +export enum ObjectType { + TYPE_3D = '3d', + TYPE_RECT = 'rect', + TYPE_BOX2D = 'box2d', + // new + TYPE_3D_BOX = '3D_BOX', + TYPE_2D_RECT = '2D_RECT', + TYPE_2D_BOX = '2D_BOX', +} + +// export interface IModelRun { +// id?: string; +// modelClass?: string; +// modelRunLabel?: string; +// } +export enum SourceType { + TASK = 'TASK', + DATA_FLOW = 'DATA_FLOW', + MODEL = 'MODEL', +} + +export interface IResultSource { + name: string; + sourceId: string; + sourceType: SourceType; + modelId?: string; + modelName?: string; +} + +export interface IObjectV2 { + id?: string; + type?: ObjectType; + version?: number; + createdBy?: number; + createdAt?: string; + + trackId?: string; + trackName?: string; + classId?: string; + className?: string; + backId?: string; + frontId?: string; + classType?: string; + classValues?: any[]; + // classValues?: Record; + modelConfidence?: number; + modelClass?: string; + contour: { + viewIndex?: number; + pointN?: number; + points?: THREE.Vector3[] | THREE.Vector2[]; + center3D?: THREE.Vector3; + rotation3D?: THREE.Vector3; + size3D?: THREE.Vector3; + [k: string]: any; + }; + meta?: { + [k: string]: any; + }; + // other + sourceId?: string; + sourceType?: string; +} +export interface IUserData { + id?: string; + // track id + trackId?: string; + trackName?: string; + isProjection?: boolean; + // isStandard?: boolean; + backId?: string; + // model + confidence?: number; + modelRun?: string; + modelClass?: string; + modelRunLabel?: string; + project?: string; + classType?: string; + classId?: string; + attrs?: Record; + // info + pointN?: number; + // [key: string]: any; + version?: number; + createdBy?: any; + createdAt?: string; + sourceId?: string; + sourceType?: string; +} + +export interface IObject extends IUserData { + frontId?: string; + uuid?: string; + objType: ObjectType; + viewIndex: number; + + points: THREE.Vector3[] | THREE.Vector2[]; + center3D: THREE.Vector3; + rotation3D: THREE.Vector3; + size3D: THREE.Vector3; + [key: string]: any; +} + +// export interface IUserInfo { +// id: string; +// } + +export interface IAttr { + id: string; + type: AttrType; + name: string; + label?: string; + required: boolean; + options: { value: any; label: string }[]; + + classId: string; + parent: string; + parentAttr: string; + parentValue: any; + key: string; + value: any; + leafFlag?: boolean; +} + +export interface IClassType { + id: string; + label: string; + name: string; + color: string; + type?: '' | 'constraint' | 'standard'; + size3D?: THREE.Vector3; + sizeMin?: THREE.Vector3; + sizeMax?: THREE.Vector3; + points?: [number, number]; + attrs: IAttr[]; +} + +export interface IImgViewConfig { + cameraInternal: { fx: number; fy: number; cx: number; cy: number }; + cameraExternal: number[]; + imgSize: [number, number]; + imgUrl: string; + imgObject: HTMLImageElement; + // rowMajor?: boolean; + name: string; +} + +export interface IConfig { + // prefix + imgViewPrefix: string; + singleViewPrefix: string; + // + showClassView: boolean; + showImgView: boolean; + showSingleImgView: boolean; + showSideView: boolean; + showOperationView: boolean; + showLabel: boolean; + // showCheckView: boolean; + // showAnnotation: boolean; + showAttr: boolean; + enableShowAttr: boolean; + // img view info + filter2DByTrack: boolean; + singleImgViewIndex: number; + imgRegionIndex: number; + // tool info + activeRect: boolean; + active3DBox: boolean; + active2DBox: boolean; + activeAnnotation: boolean; + activeTranslate: boolean; + activeTrack: boolean; + // project + projectPoint4: boolean; + projectPoint8: boolean; + projectMap3d: boolean; + + // + // type: string; + pointSize: number; + heightRange: [number, number]; + groundEnable: boolean; + // setting + pointColorMode: 'height' | 'intensity'; + pointIntensity: [number, number]; + pointGround: number; + pointInfo: IPointInfo; + pointColors: string[]; + pointHeight: [number, number]; + // renderProjectRect: boolean; + renderRect: boolean; + renderBox: boolean; + renderProjectBox: boolean; + renderProjectPoint: boolean; + withoutTaskId: string; + // + FILTER_ALL: string; + aspectRatio: number; + maxViewHeight: string; + maxViewWidth: string; + limitRect2Image: boolean; +} + +export interface IAnnotationInfo { + id: string; + msg: string; + position?: Vector3; + objectId?: string; +} + +export interface IPointInfo { + count: number; + vCount: number; + min: Vector3; + max: Vector3; + hasIntensity: boolean; + intensityRange: Vector2; +} + +export enum StatusType { + Default = '', + Create = 'Create', + Pick = 'Pick', + Loading = 'Loading', + Modal = 'Modal', + Confirm = 'Confirm', + // play + Play = 'Play', +} + +export interface IAnnotationTag { + label: string; + value: string; + id: string; +} + +export interface IModel { + id: string; + name: string; + version: string; + classes: { value: string; label: string }[]; + code: string; +} + +export type LoadStatus = '' | 'loading' | 'complete' | 'error'; + +export interface IModelResult { + recordId: string; + id: string; + version: string; + state?: LoadStatus; + config?: Record; +} + +export interface IClassification { + id: string; + name: string; + label?: string; + attrs: IClassificationAttr[]; +} + +export interface IClassificationAttr { + classificationId: string; + parent: string; + parentAttr: string; + parentValue: any; + key: string; + id: string; + type: AttrType; + name: string; + label?: string; + required: boolean; + options: { value: any; label: string }[]; + value: any; + leafFlag?: boolean; +} + +export interface IFrame { + datasetId?: string; + sceneId?: string; + id: string; + teamId?: string; + loadState: LoadStatus; + // model + model?: IModelResult; + // classification values + classifications: IClassification[]; + // save + needSave: boolean; + resultExist?: boolean; + // [k: string]: any; + // flow + dataStatus: 'INVALID' | 'VALID'; + annotationStatus: 'ANNOTATED' | 'NOT_ANNOTATED' | 'INVALID'; + sources?: IResultSource[]; + skipped: boolean; +} + +export interface ITextItem { + id: string; + role: string; + text: string; + // 点赞 + // 踩 +} + +export interface IAnnotationItem { + id: string; + msg: string; + type: 'position' | 'object'; + data: any; + step: { + id: string; + name: string; + }; + comments: number; + dataId: string; + itemIndex: number; + customValue: any; + time: number; + isResolve: boolean; + tags: { id: string; name: string; key: string }[]; +} + +export interface IFilter { + value?: string; + label: string; + type: '' | 'project' | 'model'; + options?: { value: string; label: string }[]; +} + +export interface IModelClass { + label: string; + value: string; + selected: boolean; +} + +export interface IModelConfig { + confidence: number[]; + predict: boolean; + classes: { [key: string]: IModelClass[] }; + model: string; + loading: boolean; + start: number; + duration: number; +} + +export type PointAttr = 'position' | 'color' | string; + +export interface IDataResource { + viewConfig: IImgViewConfig[]; + time: number; + // position + pointsUrl: string; + pointsData: Record; + intensityRange?: [number, number]; + ground?: number; + name?: string; +} + +export interface IResourceLoader { + data: IFrame; + getResource: () => Promise; + onProgress?: (percent: number) => void; +} + +export interface IFileConfig { + fileId: string; + name: string; + url: string; + type: string; + size: number; +} + +export interface ICheckStatus { + frameIndex: number; + hasObject: boolean; + invisibleFlag: boolean; + loadState: LoadStatus; +} + +export interface ICheckConfig { + type: '3d' | '2d'; + axis: 'z' | '-x' | '-y'; + trackId: string; + imageIndex: number; + imageMaxIndex: number; + showImageMax: boolean; + showAttr: boolean; + showAttrType: 'single' | 'multi'; + status3D: ICheckStatus[]; + status2D: ICheckStatus[]; + subViewWidth: number; + subViewHeight: number; + subViewScale: number; +} diff --git a/frontend/text-tool/src/packages/pc-editor/uitype.ts b/frontend/text-tool/src/packages/pc-editor/uitype.ts new file mode 100644 index 00000000..ac9d8496 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/uitype.ts @@ -0,0 +1,28 @@ +export interface IModalOption { + title: string; + width?: number; + data?: any; + closable?: boolean; +} + +export interface IConfirmOption { + title: string; + subTitle: string; + okText?: string; + cancelText?: string; + okDanger?: boolean; + centered?: boolean; +} + +export interface ILoadingOption { + type: 'loading' | 'error'; + content: string; +} + +export type MsgType = 'error' | 'warning' | 'success'; + +export type RegisterFn = (name: string, modal: any) => void; +export type ModalFn = (name: string | false, option?: IModalOption) => Promise; +export type MsgFn = (type: MsgType, msg: string) => void; +export type ConfirmFn = (config: IConfirmOption) => Promise; +export type LoadingFn = (config: ILoadingOption | boolean) => void; diff --git a/frontend/text-tool/src/packages/pc-editor/utils/classType.ts b/frontend/text-tool/src/packages/pc-editor/utils/classType.ts new file mode 100644 index 00000000..ed1ace42 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/utils/classType.ts @@ -0,0 +1,36 @@ +import { AttrType, IAttr, IClassType } from "../type"; + +export function isClassAttrHasValue(attr: IAttr) { + if (attr.type === AttrType.MULTI_SELECTION) { + return Array.isArray(attr.value) && attr.value.length > 0; + } else { + return !!attr.value; + } +} + +export function isClassAttrVisible( + attr: IAttr, + attrMap: Record, +): boolean { + if (!attr.parent) return true; + let parentAttr = attrMap[attr.parent]; + let visible = + parentAttr.type !== AttrType.MULTI_SELECTION + ? parentAttr.value === attr.parentValue + : (parentAttr.value as any[]).indexOf(attr.parentValue) >= 0; + + return visible && isClassAttrVisible(parentAttr, attrMap); +} +export function copyClassAttrs(classType: IClassType, valueMap: Record = {}) { + let copyClassAttrs = JSON.parse(JSON.stringify(classType.attrs)) as IAttr[]; + copyClassAttrs.forEach((attr) => { + attr.value = attr.type === AttrType.MULTI_SELECTION ? [] : ''; + if (valueMap[attr.id]) { + if (attr.type === AttrType.MULTI_SELECTION && !Array.isArray(valueMap[attr.id])) { + valueMap[attr.id] = [valueMap[attr.id]]; + } + attr.value = valueMap[attr.id]; + } + }); + return copyClassAttrs; +} \ No newline at end of file diff --git a/frontend/text-tool/src/packages/pc-editor/utils/classification.ts b/frontend/text-tool/src/packages/pc-editor/utils/classification.ts new file mode 100644 index 00000000..2f4ae662 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/utils/classification.ts @@ -0,0 +1,256 @@ +import { IAttr, IClassification, IClassificationAttr } from '../type'; +import { AttrType, IClassType } from 'pc-editor'; +import * as THREE from 'three'; +export function traverseClassification2Arr(data: any[]) { + let classifications = [] as IClassification[]; + + data.forEach((e: any) => { + let attribute = e.attribute || e; + let classificationId = e.id + ''; + + let classification: IClassification = { + id: classificationId, + name: e.name, + label: e.name, + attrs: [], + }; + let options = attribute.options || []; + if (e.inputType) { + attribute.type = e.inputType; + } + let classificationAttr: IClassificationAttr = { + id: attribute.id, + key: attribute.name, + classificationId, + parent: '', + parentValue: '', + parentAttr: e.name, + type: attribute.type, + name: attribute.name, + label: attribute.name, + value: attribute.type === AttrType.MULTI_SELECTION ? [] : '', + required: attribute.required, + options: options.map((e: any) => { + return { value: e.name, label: e.name }; + }), + }; + + classification.attrs.push(classificationAttr); + options.forEach((option: any) => { + traverseOption(classification, option, classificationAttr.id, attribute.name); + }); + classifications.push(classification); + }); + + return classifications; + + function traverseOption( + classification: IClassification, + option: any, + parent: string, + parentAttr: string, + ) { + if (!option.attributes || option.attributes.length === 0) return; + + option.attributes.forEach((attr: any) => { + let name = attr.name; + let classificationAttr: IClassificationAttr = { + id: attr.id, + key: `${parent}[${option.name}]-${name}`, + classificationId: classification.id, + parent, + parentAttr, + parentValue: option.name, + type: attr.type, + name, + label: name, + value: attr.type === AttrType.MULTI_SELECTION ? [] : '', + required: attr.required, + options: attr.options.map((e: any) => { + return { value: e.name, label: e.name }; + }), + }; + classification.attrs.push(classificationAttr); + (attr.options || []).forEach((option: any) => { + traverseOption(classification, option, classificationAttr.id, name); + }); + }); + } +} + +export function traverseClass2Arr(data: any) { + let classTypes = [] as IClassType[]; + data.forEach((config: any) => { + let classType: IClassType = { + id: config.id || config.name, + name: config.name || '', + // label: config.name + '-label', + label: config.name || '', + color: config.color || '#ff0000', + attrs: [], + type: '', + }; + + let attributes = config.attributes || []; + let toolOption = config.toolTypeOptions || {}; + if (toolOption.isStandard) { + classType.type = 'standard'; + classType.size3D = new THREE.Vector3( + toolOption.length || 0, + toolOption.width || 0, + toolOption.height || 0, + ); + } else if (toolOption.isConstraints) { + let length, width, height; + length = toolOption.length || []; + width = toolOption.width || []; + height = toolOption.height || []; + classType.type = 'constraint'; + classType.sizeMin = new THREE.Vector3(length[0] || 0, width[0] || 0, height[0] || 0); + classType.sizeMax = new THREE.Vector3(length[1] || 0, width[1] || 0, height[1] || 0); + } + + if (toolOption.points) { + classType.points = [toolOption.points, 0]; + } + + attributes.forEach((option: any) => { + let classAttr: IAttr = { + id: option.id || option.name, + name: option.name, + classId: classType.id, + label: option.name, + required: option.required, + type: option.type, + options: (option.options || []).map((e: any) => { + return { value: e.name, label: e.name }; + }), + parent: '', + parentValue: '', + parentAttr: config.name, + key: option.name, + value: option.type === AttrType.MULTI_SELECTION ? [] : '', + }; + classType.attrs.push(classAttr); + + option.options.forEach((o: any) => { + traverseOption(classType, o, classAttr.id, option.name); + }); + }); + + classTypes.push(classType); + }); + + function traverseOption(classType: IClassType, option: any, parent: any, parentAttr: string) { + // console.log(option); + if (!option.attributes || option.attributes.length === 0) return; + + option.attributes.forEach((attr: any) => { + let name = attr.name; + let classAttr: IAttr = { + id: attr.id || name, + key: `${parent}[${option.name}]-${name}`, + classId: classType.id, + parent, + parentAttr, + parentValue: option.name, + type: attr.type, + name, + label: name, + value: attr.type === AttrType.MULTI_SELECTION ? [] : '', + required: attr.required, + options: attr.options.map((e: any) => { + return { value: e.name, label: e.name }; + }), + }; + classType.attrs.push(classAttr); + (attr.options || []).forEach((option: any) => { + traverseOption(classType, option, classAttr.id, name); + }); + }); + } + + return classTypes; +} +export function copyClassification( + baseClassifications: IClassification[], + valueMap: Record, +) { + let classifications = [] as IClassification[]; + baseClassifications.forEach((classification) => { + let copyClassification = {} as IClassification; + copyClassification = JSON.parse(JSON.stringify(classification)); + + copyClassification.attrs.forEach((attr) => { + attr.value = attr.type === AttrType.MULTI_SELECTION ? [] : ''; + if (valueMap[attr.id]) { + // 处理成数组 + if (attr.type === AttrType.MULTI_SELECTION && !Array.isArray(valueMap[attr.id])) { + valueMap[attr.id] = [valueMap[attr.id]]; + } + attr.value = valueMap[attr.id]; + } + }); + classifications.push(copyClassification); + }); + return classifications; +} + +export function classificationToSave(classification: IClassification) { + let attrMap = {} as Record; + classification.attrs.forEach((attr) => { + attrMap[attr.id] = attr; + }); + let attrs = classification.attrs.filter((e) => isAttrVisible(e, attrMap) && isAttrHasValue(e)); + + // find leaf + attrs.forEach((e) => (e.leafFlag = true)); + attrs.forEach((e) => { + let parent = e.parent && attrMap[e.parent] ? attrMap[e.parent] : null; + if (parent) parent.leafFlag = false; + }); + let data = attrs.map((e) => { + const isParentMulti = e.parent && attrMap[e.parent]?.type === AttrType.MULTI_SELECTION; + return { + id: e.id, + pid: e.parent ? e.parent : null, + name: e.name, + value: e.value, + alias: e.label, + pvalue: isParentMulti ? e.parentValue : undefined, + type: e.type, + isLeaf: !!e.leafFlag, + }; + }); + + return data; +} +export function isAttrVisible( + attr: IClassificationAttr, + attrMap: Record, +): boolean { + if (!attr.parent) return true; + let parentAttr = attrMap[attr.parent]; + let visible = + parentAttr.type !== AttrType.MULTI_SELECTION + ? parentAttr.value === attr.parentValue + : (parentAttr.value as any[]).indexOf(attr.parentValue) >= 0; + + return visible && isAttrVisible(parentAttr, attrMap); +} +export function isAttrHasValue(attr: IClassificationAttr) { + if (attr.type === AttrType.MULTI_SELECTION) { + return Array.isArray(attr.value) && attr.value.length > 0; + } else { + return !!attr.value; + } +} +export function saveToClassificationValue(data: any[]) { + let values = {} as Record; + data.forEach((v) => { + // 忽略老数据 + if (Array.isArray(v)) return; + values[v.id] = v.value; + }); + return values; +} diff --git a/frontend/text-tool/src/packages/pc-editor/utils/common.ts b/frontend/text-tool/src/packages/pc-editor/utils/common.ts new file mode 100644 index 00000000..890af5e4 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/utils/common.ts @@ -0,0 +1,163 @@ +import { IFrame, IFileConfig } from '../type'; +import { IImgViewConfig, IUserData } from 'pc-editor'; +import DataManager from '../common/DataManager'; +import * as THREE from 'three'; + +export function isMatrixColumnMajor(elements: number[]) { + let rightZero = elements[3] === 0 && elements[7] === 0 && elements[11] === 0; + let bottomHasOne = !!elements[12] || !!elements[13] || !!elements[14]; + return rightZero && bottomHasOne; +} + +export function translateCameraConfig(info: any) { + let cameraExternal = info.cameraExternal || info.camera_external; + let cameraInternal = info.cameraInternal || info.camera_internal; + + if (!info || !cameraExternal || cameraExternal.length !== 16) return null; + + // to rowMajor + if (info.rowMajor === false || isMatrixColumnMajor(cameraExternal)) { + let matrix = new THREE.Matrix4(); + matrix.elements = cameraExternal; + matrix.transpose(); + cameraExternal = matrix.elements; + } + + return { cameraExternal, cameraInternal }; +} + +export function clamRange(v: number, min: number, max: number) { + return Math.max(Math.min(max, v), min); +} + +export function createViewConfig(fileConfig: IFileConfig[], cameraInfo: any[]) { + let viewConfig = [] as IImgViewConfig[]; + let pointsUrl = ''; + + fileConfig.forEach((e) => { + if (e.dirName === 'pointCloud') { + pointsUrl = e.url; + } else if (e.dirName.startsWith('image')) { + let index = +e.dirName.replace('image', ''); + viewConfig[index] = { + cameraInternal: { fx: 0, fy: 0, cx: 0, cy: 0 }, + cameraExternal: [], + imgSize: [0, 0], + imgUrl: e.url, + name: e.name, + imgObject: null as any, + }; + } + }); + + viewConfig.forEach((config, index) => { + let info = cameraInfo[index]; + + let translateInfo = translateCameraConfig(info); + if (!translateInfo) return; + + config.cameraExternal = translateInfo.cameraExternal; + config.cameraInternal = translateInfo.cameraInternal; + config.imgSize = [info.width, info.height]; + // config.rowMajor = info.rowMajor; + }); + + // filter + viewConfig = viewConfig.filter((e) => e.cameraExternal.length === 16 && e.cameraInternal); + + return { pointsUrl, config: viewConfig }; +} + +export function rand(start: number, end: number) { + return (Math.random() * (end - start) + start) | 0; +} + +export function empty(value: any) { + return value === null || value === undefined || value === ''; +} + +export function queryStr(data: Record = {}) { + let queryArr = [] as string[]; + Object.keys(data).forEach((name) => { + let value = data[name]; + if (Array.isArray(value)) { + value.forEach((e) => { + queryArr.push(`${name}=${e}`); + }); + } else { + queryArr.push(`${name}=${value}`); + } + }); + + return queryArr.join('&'); +} + +export function getTrackObject(dataInfos: IFrame[], dataManager: DataManager) { + let trackObjects = {} as Record; + let idMap = {} as Record; + + let maxNum = 0; + dataInfos.forEach((data) => { + let objects = dataManager.getFrameObject(data.id) || []; + objects.forEach((object) => { + let userData = object.userData as IUserData; + let trackName = userData.trackName; + let trackId = userData.trackId; + if (!trackName || !trackId) return; + + let trackNumber = parseInt(trackName); + if (isNaN(trackNumber)) return; + + let id = `${trackName}####${trackId}`; + if (idMap[id]) return; + + maxNum = Math.max(maxNum, trackNumber); + if (!trackObjects[trackNumber]) { + trackObjects[trackNumber] = []; + } + + trackObjects[trackNumber].push({ id: trackId, name: trackName }); + idMap[id] = true; + }); + }); + + let list = [] as { id: string; name: string }[]; + + [...Array(maxNum + 1)].forEach((e, index) => { + let objects = trackObjects[index]; + if (objects) { + list.push(...objects); + } + }); + + return list; +} + +export function formatNumDot(str: string | number, precision: number = 2): string { + str = '' + str; + let regex = /(?!^)(?=(\d{3})+(\.|$))/g; + str.replace(regex, ','); + + if (precision) { + return (+str).toFixed(precision); + } else { + return str; + } +} + +export function formatNumStr(str: string | number, precision: number = 2): string { + str = '' + str; + if (precision) { + return (+str).toFixed(precision); + } else { + return str; + } +} + +export function pickAttrs(obj: any, attrs: string[]) { + let newObj = {}; + attrs.forEach((attr) => { + newObj[attr] = obj[attr]; + }); + return newObj; +} diff --git a/frontend/text-tool/src/packages/pc-editor/utils/create.ts b/frontend/text-tool/src/packages/pc-editor/utils/create.ts new file mode 100644 index 00000000..98eb1619 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/utils/create.ts @@ -0,0 +1,63 @@ +import * as THREE from 'three'; +import Editor from '../Editor'; +import { IUserData } from '../type'; +import { nanoid } from 'nanoid'; +import { Box, Rect, Box2D, Vector2Of4 } from 'pc-render'; + +export function setIdInfo(editor: Editor, userData: IUserData) { + if (!userData.id) userData.id = THREE.MathUtils.generateUUID(); + if (!userData.trackId) { + userData.trackId = nanoid(16); + } + if (!userData.trackName) { + userData.trackName = editor.getId(); + } +} + +export function createAnnotate3D( + editor: Editor, + position: THREE.Vector3, + scale: THREE.Vector3, + rotation: THREE.Euler, + userData: IUserData = {}, +) { + let object = new Box(); + object.position.copy(position); + object.scale.copy(scale); + object.rotation.copy(rotation); + object.userData = userData; + object.matrixAutoUpdate = true; + object.updateMatrixWorld(); + // object.dashed = !!userData.invisibleFlag; + + // setIdInfo(editor, userData); + return object; +} + +export function createAnnotateRect( + editor: Editor, + center: THREE.Vector2, + size: THREE.Vector2, + userData: IUserData = {}, +) { + let object = new Rect(center, size); + object.userData = userData; + // object.dashed = !!userData.invisibleFlag; + // setIdInfo(editor, userData); + + return object; +} + +export function createAnnotateBox2D( + editor: Editor, + positions1: Vector2Of4, + positions2: Vector2Of4, + userData: IUserData = {}, +) { + let object = new Box2D(positions1, positions2); + object.userData = userData; + // object.dashed = !!userData.invisibleFlag; + // setIdInfo(editor, userData); + + return object; +} diff --git a/frontend/text-tool/src/packages/pc-editor/utils/data.ts b/frontend/text-tool/src/packages/pc-editor/utils/data.ts new file mode 100644 index 00000000..104bd7da --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/utils/data.ts @@ -0,0 +1,200 @@ +import { Box, AnnotateObject, ITransform } from 'pc-render'; +import { IUserData, IFrame, Const, Editor, IObject } from 'pc-editor'; +import { convertObject2Annotate } from './result'; +import * as THREE from 'three'; +import { setIdInfo } from './create'; + +// export function copyData(editor: Editor, copyId: string, toIds: string[], objects: Box[]) { +// // let { dataManager, editor, state } = tool; +// // let { frames } = editor.state; + +// let updateDatas = { objects: [], data: [] } as { objects: AnnotateObject[]; data: IUserData[] }; +// let updateTrans = { objects: [], transforms: [] } as { +// objects: Box[]; +// transforms: ITransform[]; +// }; +// let addDatas = [] as { objects: AnnotateObject[]; frame: IFrame }[]; + +// // debugger; +// toIds.forEach((id) => { +// if (id === copyId) return; + +// let frame = editor.getFrame(id); +// let oldObjects = editor.dataManager.dataMap.get(id) || []; +// let oldMap = {} as Record; +// oldObjects.forEach((e: AnnotateObject) => { +// if (!(e instanceof Box)) return; +// oldMap[e.userData.trackId] = e; +// }); + +// let addOption = { objects: [], frame: frame } as { +// objects: AnnotateObject[]; +// frame: IFrame; +// }; + +// objects.forEach((annotate) => { +// let userData = annotate.userData as Required; + +// let trackId = userData.trackId; +// let object = oldMap[trackId]; + +// if (object) { +// let updateData = object.userData as IUserData; +// updateData.trackName = userData.trackName; +// // updateData.resultType = userData.resultType; +// // updateData.isStandard = userData.isStandard; +// updateData.classType = userData.classType; +// // updateData.resultStatus = Const.Copied; +// updateData.attrs = JSON.parse(JSON.stringify(userData.attrs || {})); + +// // object.position.copy(annotate.position); +// // TODO +// (object as any).frame = frame; + +// updateDatas.objects.push(object); +// updateDatas.data.push(updateData); + +// updateTrans.objects.push(object); +// updateTrans.transforms.push({ +// position: annotate.position, +// scale: annotate.scale, +// rotation: annotate.rotation, +// }); +// } else { +// let newUserData = JSON.parse(JSON.stringify(userData)) as IUserData; +// newUserData.backId = ''; +// newUserData.pointN = undefined; +// // newUserData.resultStatus = Const.Copied; + +// // newUserData.cBy = ''; + +// object = editor.createAnnotate3D( +// annotate.position, +// annotate.scale, +// annotate.rotation, +// newUserData, +// ); + +// addOption.objects.push(object); +// editor.updateObjectRenderInfo(object); +// } +// }); + +// if (addOption.objects.length > 0) addDatas.push(addOption); +// }); + +// editor.cmdManager.withGroup(() => { +// if (addDatas.length > 0) { +// editor.cmdManager.execute('add-object', addDatas); +// } + +// if (updateDatas.objects.length > 0) { +// // updateDatas +// editor.cmdManager.execute('update-object-user-data', updateDatas); +// } + +// if (updateTrans.objects.length > 0) { +// editor.cmdManager.execute('update-transform-batch', updateTrans); +// } +// }); +// } + +export function addModelTrackData(editor: Editor, objectsMap: Record) { + let curFrame = editor.getCurrentFrame(); + let objects = editor.dataManager.getFrameObject(curFrame.id) || []; + + let curObjectMap = {} as Record; + objects.forEach((e) => { + if (e instanceof Box) curObjectMap[e.userData.trackId] = e; + }); + + let updateDatas = { objects: [], data: [] } as { objects: AnnotateObject[]; data: IUserData[] }; + let updateTrans = { objects: [], transforms: [] } as { + objects: Box[]; + transforms: ITransform[]; + }; + let addDatas = [] as { objects: AnnotateObject[]; frame: IFrame }[]; + + Object.keys(objectsMap).forEach((dataId) => { + let frame = editor.getFrame(dataId); + frame.needSave = true; + + let existMap = {} as Record; + let existObjects = editor.dataManager.getFrameObject(frame.id) || []; + existObjects.forEach((e) => { + if (e instanceof Box) existMap[e.userData.trackId] = e; + }); + + let addOption = { objects: [], frame: frame } as { + objects: AnnotateObject[]; + frame: IFrame; + }; + + let trackObjects = objectsMap[dataId] || []; + trackObjects.forEach((e: IObject) => { + let trackId = (e as any).trackId; + let oldObject = existMap[trackId]; + + if (oldObject) { + let oldUserData = editor.getObjectUserData(oldObject); + let updateUserData = {} as IUserData; + updateUserData.confidence = e.confidence; + // updateUserData.resultStatus = Const.Predicted; + + updateDatas.objects.push(oldObject); + updateDatas.data.push(updateUserData); + + let transform = {} as ITransform; + transform.position = new THREE.Vector3(e.center3D.x, e.center3D.y, e.center3D.z); + transform.rotation = new THREE.Euler( + e.rotation3D.x, + e.rotation3D.y, + e.rotation3D.z, + ); + // if (!oldUserData.isStandard && oldUserData.resultType !== Const.Fixed) { + // transform.scale = new THREE.Vector3(e.size3D.x, e.size3D.y, e.size3D.z); + // } + + updateTrans.objects.push(oldObject); + updateTrans.transforms.push(transform); + } else { + const curObject = curObjectMap[trackId]; + let userData = editor.getObjectUserData(curObject); + + e.trackId = userData.trackId; + e.trackName = userData.trackName; + e.classType = userData.classType; + e.classId = userData.classId; + // e.resultType = userData.resultType; + // e.isStandard = userData.isStandard; + // e.resultStatus = Const.Predicted; + + // if (userData.isStandard === true || userData.resultType === Const.Fixed) { + // e.size3D = curObject.scale.clone(); + // } + + let object = convertObject2Annotate([e], editor)[0]; + editor.updateObjectRenderInfo(object); + setIdInfo(editor, object.userData); + addOption.objects.push(object); + } + }); + + if (addOption.objects.length > 0) addDatas.push(addOption); + }); + + editor.cmdManager.withGroup(() => { + if (addDatas.length > 0) { + editor.cmdManager.execute('add-object', addDatas); + } + + if (updateDatas.objects.length > 0) { + // updateDatas + editor.cmdManager.execute('update-object-user-data', updateDatas); + } + + // if (updateTrans.objects.length > 0) { + // editor.cmdManager.execute('update-transform-batch', updateTrans); + // } + }); +} diff --git a/frontend/text-tool/src/packages/pc-editor/utils/error.ts b/frontend/text-tool/src/packages/pc-editor/utils/error.ts new file mode 100644 index 00000000..47e938fb --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/utils/error.ts @@ -0,0 +1,44 @@ +import Editor from '../Editor'; +import BSError from '../common/BSError'; +import Code from '../config/code'; + +function logErrorStack(err: BSError | Error) { + while (err) { + if (err instanceof BSError) { + console.log(err); + } else { + console.error(err); + } + + err = (err as any).oriError; + } +} + +function handleCode(editor: Editor, code: string) { + let msg = ''; + switch (code) { + case Code.NETWORK_ERROR: + msg = editor.lang('network-error'); + break; + case Code.LOGIN_INVALID: + msg = editor.lang('login-invalid'); + break; + } + + editor.showMsg('error', msg || editor.lang('unknown-error')); +} + +export function handleError(editor: Editor, err: BSError | Error) { + let oriError = (err as any).oriError; + + if (err instanceof BSError) { + if (err.code) { + handleCode(editor, err.code); + } else { + editor.showMsg('error', err.message || editor.lang('unknown-error')); + } + if (oriError) logErrorStack(oriError); + } else { + console.error(err); + } +} diff --git a/frontend/text-tool/src/packages/pc-editor/utils/index.ts b/frontend/text-tool/src/packages/pc-editor/utils/index.ts new file mode 100644 index 00000000..298a5293 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/utils/index.ts @@ -0,0 +1,10 @@ +export * from './common'; +export * from './data'; +export * from './position'; +export * from './classification'; +export * from './classType'; +export * from './error'; +export * from './result'; +export * from './point'; +export * from './create'; +export * from './track'; diff --git a/frontend/text-tool/src/packages/pc-editor/utils/point.ts b/frontend/text-tool/src/packages/pc-editor/utils/point.ts new file mode 100644 index 00000000..753ff791 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/utils/point.ts @@ -0,0 +1,250 @@ +import * as THREE from 'three'; +import { ITransform, IColorRangeItem, Box, Points } from 'pc-render'; +import { statisticPositionVInfo, getMaxMinInfo } from './position'; +import { Lut } from 'three/examples/jsm/math/Lut'; + +const lut = new Lut(); +const colorKeyCode = 'PointColor'; + +export function getTransformFrom3Point(points: THREE.Vector3[]) { + let xDir = new THREE.Vector3().copy(points[1]).sub(points[0]); + let yDir = new THREE.Vector3().copy(points[2]).sub(points[1]); + let center = new THREE.Vector3() + .copy(points[0]) + .add(points[1]) + .multiplyScalar(0.5) + .addScaledVector(yDir, 0.5); + + let angle = xDir.angleTo(new THREE.Vector3(1, 0, 0)); + + angle = xDir.y > 0 ? angle : -angle; + + return { + scale: new THREE.Vector3(xDir.length(), yDir.length(), 0), + position: new THREE.Vector3(center.x, center.y, 0), + rotation: new THREE.Euler(0, 0, angle), + }; +} +export function computePointN(box: Box, positions: THREE.BufferAttribute) { + let pointN = 0; + box.updateMatrixWorld(); + if (!box.geometry.boundingBox) box.geometry.computeBoundingBox(); + // console.time('update'); + let bbox = box.geometry.boundingBox; + if (bbox) { + let pos = new THREE.Vector3(); + let matrix = new THREE.Matrix4().copy(box.matrixWorld).invert(); + + for (let i = 0; i < positions.count; i++) { + let x = positions.getX(i); + let y = positions.getY(i); + let z = positions.getZ(i); + pos.set(x, y, z).applyMatrix4(matrix); + + if ( + pos.x > bbox.min.x && + pos.x < bbox.max.x && + pos.y > bbox.min.y && + pos.y < bbox.max.y && + pos.z > bbox.min.z && + pos.z < bbox.max.z + ) { + pointN++; + } + } + } + return pointN; +} +export function getMiniBox(transform: Required, positions: THREE.BufferAttribute) { + let matrix = new THREE.Matrix4(); + const quaternion = new THREE.Quaternion().setFromEuler(transform.rotation); + matrix.compose(transform.position, quaternion, transform.scale); + + let invertMatrix = new THREE.Matrix4().copy(matrix).invert(); + + let pos = new THREE.Vector3(); + let box = new THREE.Box3(new THREE.Vector3(-0.5, -0.5, -0.5), new THREE.Vector3(0.5, 0.5, 0.5)); + let newBox = new THREE.Box3(new THREE.Vector3(), new THREE.Vector3()); + let pointN = 0; + for (let i = 0; i < positions.count; i++) { + let x = positions.getX(i); + let y = positions.getY(i); + let z = positions.getZ(i); + pos.set(x, y, z).applyMatrix4(invertMatrix); + + if (box.containsPoint(pos)) { + pointN++; + if (newBox.max.length() === 0 && newBox.min.length() === 0) { + newBox.max.copy(pos); + newBox.min.copy(pos); + } else { + newBox.expandByPoint(pos); + } + } + } + + let min = newBox.min; + let max = newBox.max; + let boxSize = max.clone().sub(min).length(); + if (pointN > 0 && boxSize > 0.1) { + transform.scale.multiply( + new THREE.Vector3( + Math.abs(max.x - min.x), + Math.abs(max.y - min.y), + Math.abs(max.z - min.z), + ), + ); + let center = min.clone().add(max).divideScalar(2); + center.applyMatrix4(matrix); + transform.position.copy(center); + } + + // return newBox; +} +export function filterPosition(positions: THREE.BufferAttribute, zRange: [number, number]) { + let filterPosition = []; + for (let i = 0; i < positions.count; i++) { + let x = positions.getX(i); + let y = positions.getY(i); + let z = positions.getZ(i); + if (z <= zRange[1] && z >= zRange[0]) { + filterPosition.push(new THREE.Vector3(x, y, z)); + } + } + return filterPosition; +} +export function getMiniBox1( + transform: Required, + positions: THREE.BufferAttribute, + heightRange: [number, number], +) { + let matrix = new THREE.Matrix4(); + const quaternion = new THREE.Quaternion().setFromEuler(transform.rotation); + matrix.compose(transform.position, quaternion, transform.scale); + let invertMatrix = new THREE.Matrix4().copy(matrix).invert(); + + let pos = new THREE.Vector3(); + let box = new THREE.Box3(new THREE.Vector3(-0.5, -0.5, -0.5), new THREE.Vector3(0.5, 0.5, 0.5)); + // let newBox = new THREE.Box3(new THREE.Vector3(), new THREE.Vector3()); + // let pointN = 0; + let offsetFloor = 0.15; // ground offset + let preData: THREE.Vector3[] = []; + + setPreData(); + + if (preData.length === 0) return; + + // filter z + let info = statisticPositionVInfo(preData); + let infoRange = getMaxMinInfo(info); + // console.log('info', info, infoRange); + // ground offset + if (infoRange.infoMin + offsetFloor < infoRange.infoMax) { + infoRange.infoMin += offsetFloor; + } + if (infoRange.infoMax <= infoRange.infoMin) return; + + preData = preData.filter((e) => e.z >= infoRange.infoMin && e.z <= infoRange.infoMax); + transform.position.z = (infoRange.infoMin + infoRange.infoMax) / 2; + transform.scale.z = Math.abs(infoRange.infoMax - infoRange.infoMin); + + // update matrix + matrix.compose(transform.position, quaternion, transform.scale); + invertMatrix = new THREE.Matrix4().copy(matrix).invert(); + // to local pos + preData.forEach((pos) => { + pos.applyMatrix4(invertMatrix); + }); + + // x + info = statisticPositionVInfo(preData, 2, 'x'); + infoRange = getMaxMinInfo(info, { filter: 0 } as any); + // console.log('info', info, infoRange); + let positionX = (infoRange.infoMin + infoRange.infoMax) / 2; + transform.scale.x *= Math.abs(infoRange.infoMax - infoRange.infoMin); + + // y + info = statisticPositionVInfo(preData, 2, 'y'); + infoRange = getMaxMinInfo(info, { filter: 0 } as any); + // console.log('info', info, infoRange); + let positionY = (infoRange.infoMin + infoRange.infoMax) / 2; + transform.scale.y *= Math.abs(infoRange.infoMax - infoRange.infoMin); + + let center = new THREE.Vector3(positionX, positionY, 0); + center.applyMatrix4(matrix); + transform.position.copy(center); + + function setPreData() { + for (let i = 0; i < positions.count; i++) { + let x = positions.getX(i); + let y = positions.getY(i); + let z = positions.getZ(i); + pos.set(x, y, z).applyMatrix4(invertMatrix); + if ( + box.min.x <= pos.x && + box.max.x >= pos.x && + box.min.y <= pos.y && + box.max.y >= pos.y && + z <= heightRange[1] && + z >= heightRange[0] + ) { + preData.push(new THREE.Vector3(x, y, z)); + } + } + } +} +export function getColorRange(start: number, end: number, colors: string[]) { + let ranges: IColorRangeItem[] = []; + let offset = (end - start) / colors.length; + colors.forEach((color, index) => { + let min = start + index * offset; + let max = start + (index + 1) * offset; + + min = +min.toFixed(4); + max = +max.toFixed(4); + ranges.push({ min, max, color: new THREE.Color(color) }); + }); + + return ranges; +} +export function getColorRangeByArray( + colors: string[], + value: [number, number], + range: [number, number], + segment = 6, +) { + const len = colors.length - 1; + const lookupTable = colors.map((color, index) => { + return [index / len, color]; + }); + const mapLinear = (n: number) => THREE.MathUtils.mapLinear(n, 0, 1, value[0], value[1]); + lut.addColorMap(colorKeyCode, lookupTable as any); + lut.setColorMap(colorKeyCode, 24); + let strategy = (value: number) => + THREE.MathUtils.mapLinear(Math.asin(value * 2 - 1), -Math.PI / 2, Math.PI / 2, 0, 1); + let _colors: THREE.Color[] = []; + for (let i = 0; i < segment; i++) { + _colors.push(lut.getColor(strategy(i / (segment - 1)))); + } + + let colorMap: IColorRangeItem[] = []; + colorMap.push({ + min: range[0], + max: value[0], + color: _colors[0], + }); + + for (let i = 0; i < segment - 2; i++) { + colorMap.push({ + min: mapLinear(i / (segment - 2)), + max: mapLinear((i + 1) / (segment - 2)), + color: _colors[i + 1], + }); + } + colorMap.push({ + min: value[1], + max: range[1], + color: _colors[segment - 1], + }); + return colorMap; +} diff --git a/frontend/text-tool/src/packages/pc-editor/utils/position.ts b/frontend/text-tool/src/packages/pc-editor/utils/position.ts new file mode 100644 index 00000000..3aee7ecc --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/utils/position.ts @@ -0,0 +1,72 @@ +import { max, min } from 'lodash'; +import * as THREE from 'three'; + +export function getPositionGround(position: number[]) { + let info = statisticPositionInfo(position, 1); + + let maxKey = 0; + let maxValue = -Infinity; + Object.keys(info).forEach((attr) => { + let key = +attr; + if (info[attr] > maxValue) { + maxValue = info[attr]; + maxKey = key; + } + }); + return maxKey; +} + +export function statisticPositionInfo(position: number[], precision = 2, index = 2) { + let info = {}; + let len = position.length; + for (let i = 0; i < len; i = i + 3) { + let v = position[i + index]; + let strValue = v.toFixed(precision); + + info[strValue] = info[strValue] || 0; + info[strValue]++; + } + + return info; +} + +export function statisticPositionVInfo( + position: THREE.Vector3[], + precision = 2, + index: 'x' | 'y' | 'z' = 'z', +) { + let info = {} as Record; + let len = position.length; + for (let i = 0; i < len; i++) { + let v = position[i][index]; + let strValue = v.toFixed(precision); + + info[strValue] = info[strValue] || 0; + info[strValue]++; + } + + return info; +} + +export function getMaxMinInfo( + info: Record, + option = { filter: 1, min: -Infinity, max: Infinity }, +) { + let { filter = 1, min = -Infinity, max = Infinity } = option; + let infoMax = -Infinity; + let infoMin = Infinity; + + Object.keys(info).forEach((attr) => { + let key = +attr; + let value = info[attr]; + if (value <= filter || key < min || key > max) return; + if (key > infoMax) { + infoMax = key; + } + if (key < infoMin) { + infoMin = key; + } + }); + + return { infoMax, infoMin }; +} diff --git a/frontend/text-tool/src/packages/pc-editor/utils/result.ts b/frontend/text-tool/src/packages/pc-editor/utils/result.ts new file mode 100644 index 00000000..2c67e6be --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/utils/result.ts @@ -0,0 +1,365 @@ +import * as THREE from 'three'; +import { Box, Rect, Box2D, AnnotateObject } from 'pc-render'; +import { + IUserData, + IClassType, + Const, + IObject, + ObjectType, + IObjectV2, + AttrType, + IAttr, + SourceType, +} from '../type'; +import Editor from '../Editor'; +import * as createUtils from './create'; +import { empty } from './common'; +import { copyClassAttrs, isClassAttrHasValue, isClassAttrVisible } from './classType'; + +let position = new THREE.Vector3(); +let rotation = new THREE.Euler(); +let scale = new THREE.Vector3(); +let center = new THREE.Vector2(); +let size = new THREE.Vector2(); + +function objToArray(obj: Record = {}, classType?: IClassType) { + if (!classType) { + let data = [] as any[]; + Object.keys(obj).forEach((key) => { + let value = obj[key]; + if (empty(value)) return; + data.push({ + id: key, + pid: null, + name: '', + value: value, + alias: '', + isLeaf: true, + }); + }); + return data; + } + let copyAttrs = copyClassAttrs(classType, obj); + let attrMap = {} as Record; + copyAttrs.forEach((attr) => { + attrMap[attr.id] = attr; + }); + let attrs = copyAttrs.filter((e) => isClassAttrVisible(e, attrMap) && isClassAttrHasValue(e)); + + attrs.forEach((e) => (e.leafFlag = true)); + attrs.forEach((e) => { + let parent = e.parent && attrMap[e.parent] ? attrMap[e.parent] : null; + if (parent) parent.leafFlag = false; + }); + + let data = attrs.map((e) => { + const isParentMulti = e.parent && attrMap[e.parent]?.type === AttrType.MULTI_SELECTION; + return { + id: e.id, + pid: e.parent ? e.parent : null, + name: e.name, + value: e.value, + type: e.type, + pvalue: isParentMulti ? e.parentValue : undefined, + alias: e.label, + isLeaf: !!e.leafFlag, + }; + }); + return data; +} +export function translateToObjectV2(object: IObject, baseClassType: IClassType) { + let objectV2: IObjectV2 = { + id: object.id, + type: object.objType, + version: object.version, + createdBy: object.createdBy, + createdAt: object.createdAt, + trackId: object.trackId, + backId: object.backId, + frontId: object.id, + trackName: object.trackName, + classId: object.classId, + className: object.classType, + sourceId: object.sourceId, + sourceType: object.sourceType, + // classValues: object.attrs, + classValues: objToArray(object.attrs, baseClassType), + modelConfidence: object.confidence, + modelClass: object.modelClass, + meta: { + lastTime: object.lastTime, + updateTime: object.updateTime, + isProjection: object.isProjection, + classType: object.classType, + }, + contour: { + viewIndex: object.viewIndex, + pointN: object.pointN, + points: object.points, + }, + }; + + if (object.center3D) objectV2.contour.center3D = object.center3D; + if (object.size3D) objectV2.contour.size3D = object.size3D; + if (object.rotation3D) objectV2.contour.rotation3D = object.rotation3D; + return objectV2; +} +export function translateToObject(objectV2: IObjectV2): IObject { + let object = { + ...objectV2, + ...objectV2.meta, + ...objectV2.contour, + confidence: objectV2.modelConfidence, + objType: objectV2.type || objectV2['objType'], + attrs: arrayToObj(objectV2.classValues || []), + } as IObject; + return object; +} +function arrayToObj(data: any[] = []) { + let values = {} as Record; + + if (Array.isArray(data)) { + data.forEach((e) => { + if (Array.isArray(e)) return; + values[e.id] = e.value; + }); + } + + return values; +} +function bindInfo(target: any, info: IObject) { + Object.assign(target, { + lastTime: info.lastTime, + updateTime: info.updateTime, + createdAt: info.createdAt, + createdBy: info.createdBy, + }); +} +export function convertObject2Annotate(objects: IObject[], editor: Editor) { + let annotates = [] as AnnotateObject[]; + // let classMap = {} as Record; + // editor.state.classTypes.forEach((e) => { + // classMap[e.name] = e; + // }); + objects.forEach((obj) => { + let userData = {} as IUserData; + let objType = obj.objType || obj.type; + let classConfig = editor.getClassType(obj.classId as string); + if (!obj.classId && obj.classType) { + classConfig = editor.getClassType(obj.classType); + } + userData.id = obj.id; + userData.backId = obj.backId; + // userData.invisibleFlag = obj.invisibleFlag; + // userData.refId = obj.refId; + userData.isProjection = obj.isProjection || false; + // userData.isStandard = obj.isStandard || false; + userData.trackId = obj.trackId || ''; + userData.trackName = obj.trackName || ''; + + userData.classType = classConfig?.name || ''; + userData.classId = obj.classId || ''; + userData.confidence = obj.confidence || undefined; + userData.modelClass = obj.modelClass || ''; + userData.modelRun = obj.modelRun || ''; + userData.modelRunLabel = obj.modelRunLabel || ''; + userData.sourceId = obj.sourceId; + userData.sourceType = obj.sourceType; + userData.attrs = obj.attrs || {}; + userData.pointN = obj.pointN || 0; + if (objType === ObjectType.TYPE_3D_BOX || objType === ObjectType.TYPE_3D) { + position.set(obj.center3D.x, obj.center3D.y, obj.center3D.z); + rotation.set(obj.rotation3D.x, obj.rotation3D.y, obj.rotation3D.z); + scale.set(obj.size3D.x, obj.size3D.y, obj.size3D.z); + + let box = createUtils.createAnnotate3D(editor, position, scale, rotation, userData); + if (obj.frontId) box.uuid = obj.frontId; + if (classConfig) { + box.color.setStyle(classConfig.color); + // box.editConfig.resize = !userData.isStandard && userData.resultType !== Const.Fixed; + } + bindInfo(box, obj); + annotates.push(box); + } else if ( + objType === ObjectType.TYPE_2D_RECT || + objType === ObjectType.TYPE_RECT + ) { + let bbox = getBBox(obj.points as any); + center.set((bbox.xMax + bbox.xMin) / 2, (bbox.yMax + bbox.yMin) / 2); + size.set(bbox.xMax - bbox.xMin, bbox.yMax - bbox.yMin); + let rect = createUtils.createAnnotateRect(editor, center, size, userData); + + rect.viewId = `${editor.state.config.imgViewPrefix}-${obj.viewIndex}`; + if (classConfig) rect.color = classConfig.color; + bindInfo(rect, obj); + annotates.push(rect); + } else if ( + objType === ObjectType.TYPE_2D_BOX || + objType === ObjectType.TYPE_BOX2D + ) { + let positions1 = [] as THREE.Vector2[]; + let positions2 = [] as THREE.Vector2[]; + obj.points.forEach((e: any, index: number) => { + if (index < 4) { + positions1.push(new THREE.Vector2(e.x, e.y)); + } else { + positions2.push(new THREE.Vector2(e.x, e.y)); + } + }); + let box2d = createUtils.createAnnotateBox2D( + editor, + positions1 as any, + positions2 as any, + userData, + ); + if (obj.frontId) box2d.uuid = obj.frontId; + box2d.viewId = `${editor.state.config.imgViewPrefix}-${obj.viewIndex}`; + if (classConfig) box2d.color = classConfig.color; + bindInfo(box2d, obj); + annotates.push(box2d); + } + }); + + return annotates; +} + +function getBBox(points: THREE.Vector2[]) { + let xMin = Infinity; + let xMax = -Infinity; + let yMin = Infinity; + let yMax = -Infinity; + points.forEach((p) => { + if (p.x < xMin) xMin = p.x; + if (p.x > xMax) xMax = p.x; + if (p.y > yMax) yMax = p.y; + if (p.y < yMin) yMin = p.y; + }); + return { xMax, xMin, yMax, yMin }; +} +export function updateObjectVersion(obj: any) { + let version = obj.version || 1; + if (!obj.version) { + version = 1; + } else if (obj.updateTime > obj.lastTime) { + version++; + } + obj.lastTime = obj.updateTime; + obj.version = version; +} +export function convertAnnotate2Object(annotates: AnnotateObject[], editor: Editor) { + let objects = [] as IObject[]; + + annotates.forEach((obj) => { + let userData = editor.getObjectUserData(obj) as Required; + let points = obj instanceof Box ? [] : get2DPoints(obj as any); + let classConfig = editor.getClassType(userData); + updateObjectVersion(obj as any); + let bsObj = obj as any; + let info: IObject = { + frontId: obj.uuid, + uuid: userData.backId || undefined, + objType: getObjType(obj), + version: bsObj.version, + createdBy: bsObj.createdBy, + createdAt: bsObj.createdAt, + id: userData.id, + // refId: userData.refId || '', + // invisibleFlag: !!userData.invisibleFlag, + isProjection: userData.isProjection || false, + // isStandard: userData.isStandard || false, + trackId: userData.trackId || '', + trackName: userData.trackName || '', + // resultStatus: userData.resultStatus || '', + // resultType: userData.resultType || '', + classId: classConfig ? classConfig.id : '', + classType: classConfig ? classConfig.name : '', + confidence: userData.confidence || undefined, + modelRun: userData.modelRun || '', + modelClass: userData.modelClass || '', + modelRunLabel: userData.modelRunLabel || '', + sourceId: userData.sourceId || editor.state.config.withoutTaskId, + sourceType: userData.sourceType || SourceType.DATA_FLOW, + points: points, + pointN: userData.pointN || 0, + viewIndex: 0, + attrs: userData.attrs || {}, + center3D: new THREE.Vector3(), + rotation3D: new THREE.Vector3(), + size3D: new THREE.Vector3(), + }; + + if (obj instanceof Box) { + info.center3D.set(obj.position.x, obj.position.y, obj.position.z); + info.rotation3D.set(obj.rotation.x, obj.rotation.y, obj.rotation.z); + info.size3D.set(obj.scale.x, obj.scale.y, obj.scale.z); + } else { + info.viewIndex = parseInt((obj.viewId.match(/[0-9]{1,5}$/) as any)[0]); + } + objects.push(info); + }); + + // console.log(objects); + + return objects; +} + +function getObjType(annotate: AnnotateObject): ObjectType { + let type: ObjectType = ObjectType.TYPE_3D_BOX; + if (annotate instanceof Box) type = ObjectType.TYPE_3D_BOX; + else if (annotate instanceof Rect) type = ObjectType.TYPE_2D_RECT; + else if (annotate instanceof Box2D) type = ObjectType.TYPE_2D_BOX; + return type; +} + +export function get2DPoints(object: Rect | Box2D) { + let points = [] as THREE.Vector2[]; + if (object instanceof Rect) { + let { size, center } = object; + points = [ + new THREE.Vector2(center.x - size.x / 2, center.y - size.y / 2), + new THREE.Vector2(center.x - size.x / 2, center.y + size.y / 2), + new THREE.Vector2(center.x + size.x / 2, center.y + size.y / 2), + new THREE.Vector2(center.x + size.x / 2, center.y - size.y / 2), + ]; + } else if (object instanceof Box2D) { + points = [...object.positions1, ...object.positions2]; + } + return points; +} + +export function get3DPoints8(object: Box) { + let positionsFrontV3 = [...Array(4)].map((e) => new THREE.Vector3()); + let positionsBackV3 = [...Array(4)].map((e) => new THREE.Vector3()); + + let bbox = object.geometry.boundingBox as THREE.Box3; + + getPositions(bbox, positionsFrontV3, positionsBackV3); + + positionsFrontV3.forEach((v) => { + v.applyMatrix4(object.matrixWorld); + }); + + positionsBackV3.forEach((v) => { + v.applyMatrix4(object.matrixWorld); + }); + + return [...positionsFrontV3, ...positionsBackV3]; + + function getPositions( + box: THREE.Box3, + positionsFront: THREE.Vector3[], + positionsBack: THREE.Vector3[], + ) { + // front + positionsFront[0].set(box.max.x, box.min.y, box.max.z); + positionsFront[1].set(box.max.x, box.min.y, box.min.z); + positionsFront[2].set(box.max.x, box.max.y, box.min.z); + positionsFront[3].set(box.max.x, box.max.y, box.max.z); + + // back + positionsBack[0].set(box.min.x, box.min.y, box.max.z); + positionsBack[1].set(box.min.x, box.min.y, box.min.z); + positionsBack[2].set(box.min.x, box.max.y, box.min.z); + positionsBack[3].set(box.min.x, box.max.y, box.max.z); + } +} diff --git a/frontend/text-tool/src/packages/pc-editor/utils/track.ts b/frontend/text-tool/src/packages/pc-editor/utils/track.ts new file mode 100644 index 00000000..4c24c21b --- /dev/null +++ b/frontend/text-tool/src/packages/pc-editor/utils/track.ts @@ -0,0 +1,30 @@ +import { IObject } from '../type'; + +export function getTrackFromObject(info: Record) { + let globalTrack = {} as Record>; + // let frameTrack = {} as Record>>; + + Object.keys(info).forEach((frameId) => { + let objects = info[frameId] || []; + + // frameTrack[frameId] = frameTrack[frameId] || {}; + + objects.forEach((obj) => { + let trackId = obj.trackId as string; + + if (!globalTrack[trackId]) { + globalTrack[trackId] = { + trackName: obj.trackName, + trackId: obj.trackId, + classType: obj.classType, + classId: obj.classId, + resultType: obj.resultType, + }; + } else { + Object.assign(obj, globalTrack[trackId]); + } + }); + }); + + return { globalTrack }; +} diff --git a/frontend/text-tool/src/packages/pc-render/PointCloud.ts b/frontend/text-tool/src/packages/pc-render/PointCloud.ts new file mode 100644 index 00000000..def73d41 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/PointCloud.ts @@ -0,0 +1,317 @@ +import * as THREE from 'three'; +import RenderView from './renderView/Render'; +// import { PCDLoader } from 'three/examples/jsm/loaders/PCDLoader.js'; +import { Box, Rect, Box2D } from './objects'; +import { Event } from './config'; +import { AnnotateObject, Object2D } from './objects'; +// import { PCDLoader } from './loader'; +import { ITransform } from './type'; +import { isArray } from 'lodash'; +import TWEEN from '@tweenjs/tween.js'; +import PointsMaterial from './material/PointsMaterial'; +import { Points } from './points'; +import * as _ from 'lodash'; + +export default class PointCloud extends THREE.EventDispatcher { + scene: THREE.Scene; + annotate3D: THREE.Group; + annotate2D: Object2D[] = []; + groupPoints: THREE.Group; + groupTrack: THREE.Group; + selection: AnnotateObject[]; + selectionMap: Record; + renderViews: RenderView[]; + ground: THREE.PlaneHelper; + trimBox: THREE.Box3Helper; + pixelRatio: number; + // loading + loading: boolean = false; + timeStamp: number = 0; + // + material: PointsMaterial = new PointsMaterial(); + selectColor: THREE.Color = new THREE.Color(1, 0, 0); + highlightColor: THREE.Color = new THREE.Color(1, 0, 0); + private renderTimer: number = 0; + + constructor() { + super(); + + this.pixelRatio = 1; + // this.pixelRatio = window.devicePixelRatio; + this.scene = new THREE.Scene(); + // this.scene.autoUpdate = false; + this.annotate3D = new THREE.Group(); + this.groupPoints = new THREE.Group(); + this.groupTrack = new THREE.Group(); + + let ground = new THREE.PlaneHelper( + new THREE.Plane(new THREE.Vector3(0, 0, -1), 0), + 100, + 0xcccccc, + ); + this.ground = ground; + this.ground.visible = false; + + let box = new THREE.Box3( + new THREE.Vector3(-100, -100, -100), + new THREE.Vector3(100, 100, 100), + ); + this.trimBox = new THREE.Box3Helper(box, new THREE.Color(0xffff00)); + this.trimBox.visible = false; + + this.scene.add( + this.groupPoints, + this.annotate3D, + this.ground, + this.trimBox, + this.groupTrack, + ); + + const axesHelper = new THREE.AxesHelper(100); + axesHelper.visible = false; + this.scene.add(axesHelper); + + this.selection = []; + this.selectionMap = {}; + this.renderViews = []; + + // test + // this.initStats(); + + // @ts-ignore + // window.pc = this; + } + + initTween() { + requestAnimationFrame(animate); + + function animate(time: number) { + requestAnimationFrame(animate); + TWEEN.update(time); + } + } + + // general ************************************* + updateObjectTransform(object: THREE.Object3D, option: Partial) { + // console.log(scale, position) + let { scale, position, rotation } = option; + + if (scale) object.scale.copy(scale); + if (position) object.position.copy(position); + if (rotation) object.rotation.copy(rotation); + this.dispatchEvent({ type: Event.OBJECT_TRANSFORM, data: { object, option } }); + this.render(); + } + + update2DRect(object: Rect, option: { center?: THREE.Vector2; size?: THREE.Vector2 }) { + // console.log(scale, position) + let { center, size } = option; + + if (center) object.center.copy(center); + if (size) object.size.copy(size); + this.dispatchEvent({ type: Event.OBJECT_2D_TRANSFORM, data: { object, option } }); + this.render(); + } + + update2DBox( + object: Box2D, + option: { + positions2?: Record; + positions1?: Record; + }, + ) { + // console.log(scale, position) + + if (option.positions1) { + Object.keys(option.positions1).forEach((index) => { + object.positions1[index as any] = (option as any).positions1[index as any]; + }); + } + + if (option.positions2) { + Object.keys(option.positions2).forEach((index) => { + object.positions2[index as any] = (option as any).positions2[index as any]; + }); + } + + this.dispatchEvent({ type: Event.OBJECT_2D_TRANSFORM, data: { object, option } }); + this.render(); + } + + selectObject(object?: AnnotateObject | AnnotateObject[]) { + let preSelection = this.selection; + let selection: AnnotateObject[] = []; + if (object) { + selection = Array.isArray(object) ? object : [object]; + } + // else { + // this.selection = []; + // } + // selection = selection.filter((item) => item.visible !== false); + this.selection = selection; + + this.selectionMap = {}; + selection.forEach((e) => { + this.selectionMap[e.uuid] = e; + }); + + this.dispatchEvent({ + type: Event.SELECT, + data: { preSelection, curSelection: this.selection }, + }); + this.render(); + } + + addObject(objects: AnnotateObject | AnnotateObject[]) { + if (!isArray(objects)) objects = [objects]; + + objects.forEach((obj) => { + // if (!obj.userData.id) obj.userData.id = THREE.MathUtils.generateUUID(); + if (obj instanceof THREE.Object3D) { + if (this.annotate3D.children.indexOf(obj) < 0) this.annotate3D.add(obj); + } else { + if (this.annotate2D.indexOf(obj) < 0) this.annotate2D.push(obj); + } + }); + this.render(); + this.dispatchEvent({ type: Event.ADD_OBJECT, data: objects }); + } + + removeObject(objects: AnnotateObject | AnnotateObject[]) { + if (!isArray(objects)) objects = [objects]; + // if (object.userData.type === ObjectType.ANNOTATE) { + + let updateSelect = false; + objects.forEach((object) => { + if (object instanceof THREE.Object3D) { + this.annotate3D.remove(object); + } else { + let index = this.annotate2D.indexOf(object); + if (index >= 0) this.annotate2D.splice(index, 1); + // this.annotate2D.remove(object); + } + + if (this.selectionMap[object.uuid]) { + updateSelect = true; + delete this.selectionMap[object.uuid]; + } + }); + + this.dispatchEvent({ type: Event.REMOVE_OBJECT, data: objects }); + + if (updateSelect) { + let selection = this.selection.filter((e) => this.selectionMap[e.uuid]); + this.selectObject(selection); + } + + this.render(); + // } + } + + setObjectUserData(objects: AnnotateObject | AnnotateObject[], options: any | any[]) { + if (!Array.isArray(objects)) objects = [objects]; + // if (!Array.isArray(options)) options = [options]; + + objects.forEach((object, index) => { + let option = Array.isArray(options) ? options[index] : options; + Object.assign(object.userData, option); + }); + + this.dispatchEvent({ type: Event.USER_DATA_CHANGE, data: { objects, options } }); + this.render(); + } + + setVisible(objects: AnnotateObject | AnnotateObject[], visible: boolean) { + if (!isArray(objects)) objects = [objects]; + + objects.forEach((object) => { + object.visible = visible; + + if (!visible && this.selection.length > 0 && this.selection[0] === object) { + this.selectObject(); + } + }); + + this.dispatchEvent({ type: Event.VISIBLE_CHANGE, data: { objects, visible } }); + this.render(); + } + + addRenderView(view: RenderView) { + if (this.renderViews.indexOf(view) > 0) return; + view.init(); + this.renderViews.push(view); + } + + removeRenderView(view: RenderView) { + let index = this.renderViews.indexOf(view); + if (index < 0) return; + + this.renderViews.splice(index, 1); + view.destroy(); + } + + getAnnotate3D() { + return this.annotate3D.children as Box[]; + } + + getAnnotate2D() { + return this.annotate2D; + } + + clearData() { + this.selectObject(); + this.annotate3D.children = []; + this.annotate2D = []; + this.dispatchEvent({ type: Event.CLEAR_DATA }); + this.render(); + } + + // ********************************************* + setPointCloudData(data: any) { + let points; + if (this.groupPoints.children.length === 0) { + points = new Points(this.material); + // points.addEventListener(Event.POINTS_CHANGE, () => { + // this.dispatchEvent({ type: Event.POINTS_CHANGE }); + // this.render(); + // }); + this.groupPoints.add(points); + } else { + points = this.groupPoints.children[0] as Points; + } + // this.dispatchEvent({ type: Event.LOAD_POINT_BEFORE }); + points.updateData(data); + this.render(); + // this.dispatchEvent({ type: Event.LOAD_POINT_AFTER }); + } + loadPointCloud(url: string, onProgress?: (percent: number) => void) { + let points; + if (this.groupPoints.children.length === 0) { + // debugger; + points = new Points(this.material); + points.addEventListener(Event.POINTS_CHANGE, () => { + this.dispatchEvent({ type: Event.POINTS_CHANGE }); + this.render(); + }); + this.groupPoints.add(points); + } else { + points = this.groupPoints.children[0] as Points; + } + return points.loadUrl(url, onProgress); + } + + // render + _render() { + this.dispatchEvent({ type: Event.RENDER_BEFORE }); + this.renderViews.forEach((view) => { + view.render(); + }); + this.renderTimer = 0; + this.dispatchEvent({ type: Event.RENDER_AFTER }); + } + + render() { + if (this.renderTimer) return; + this.renderTimer = requestAnimationFrame(this._render.bind(this)); + } +} diff --git a/frontend/text-tool/src/packages/pc-render/action/Action.ts b/frontend/text-tool/src/packages/pc-render/action/Action.ts new file mode 100644 index 00000000..ef0d15b2 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/action/Action.ts @@ -0,0 +1,12 @@ +export default class Action { + enabled: boolean = true; + constructor() {} + init() {} + destroy() {} + toggle(enabled: boolean) { + this.enabled = enabled; + } + isEnable(): boolean { + return this.enabled; + } +} diff --git a/frontend/text-tool/src/packages/pc-render/action/CreateAction.ts b/frontend/text-tool/src/packages/pc-render/action/CreateAction.ts new file mode 100644 index 00000000..39064145 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/action/CreateAction.ts @@ -0,0 +1,312 @@ +import * as THREE from 'three'; +import MainRenderView from '../renderView/MainRenderView'; +import Action from './Action'; +import Box from '../objects/Box'; +import * as _ from 'lodash'; + +interface Point { + x: number; + y: number; +} + +type ICallback = (data: any) => void; + +const TypePoints = { + 'points-1': 1, + 'points-3': 3, + 'box-2': 4, + box: 2, +}; + +type DrawType = keyof typeof TypePoints; + +interface IStartOption { + type: DrawType; + trackLine?: boolean; + startClick?: boolean; + startMouseDown?: boolean; +} + +export default class CreateAction extends Action { + static actionName: string = 'create-obj'; + renderView: MainRenderView; + canvas: HTMLCanvasElement; + context: CanvasRenderingContext2D; + points: Point[] = []; + raycaster: THREE.Raycaster; + drawType: DrawType = 'points-3'; + startClick: boolean = true; + startMouseDown: boolean = false; + trackLine: boolean = false; + callback: ICallback | undefined | null = null; + onChange: ICallback | undefined | null = null; + constructor(renderView: MainRenderView) { + super(); + + this.renderView = renderView; + this.raycaster = new THREE.Raycaster(); + + let canvas = document.createElement('canvas'); + canvas.className = 'create-obj'; + canvas.style.position = 'absolute'; + canvas.style.left = '0px'; + canvas.style.top = '0px'; + canvas.style.width = '100%'; + canvas.style.height = '100%'; + canvas.style.display = 'none'; + canvas.style.cursor = 'crosshair'; + + let context = canvas.getContext('2d') as any; + + this.canvas = canvas; + this.context = context; + + this.onClick = this.onClick.bind(this); + this.onMouseDown = this.onMouseDown.bind(this); + this.onMouseUp = this.onMouseUp.bind(this); + this.onMouseMove = this.onMouseMove.bind(this); + + this.drawBox = _.throttle(this.drawBox.bind(this), 30); + this.drawPoint3 = _.throttle(this.drawPoint3.bind(this), 30); + // this.toggle(false); + } + + init() { + let dom = this.renderView.container; + dom.appendChild(this.canvas); + // this.start(); + this.canvas.addEventListener('click', this.onClick); + this.canvas.addEventListener('mousedown', this.onMouseDown); + this.canvas.addEventListener('mouseup', this.onMouseUp); + this.canvas.addEventListener('mousemove', this.onMouseMove); + } + + destroy() { + this.canvas.removeEventListener('click', this.onClick); + this.canvas.removeEventListener('mousedown', this.onMouseDown); + this.canvas.removeEventListener('mouseup', this.onMouseUp); + this.canvas.removeEventListener('mousemove', this.onMouseMove); + } + + start(option: IStartOption, callback: ICallback, onChange?: ICallback) { + if (this.canvas.style.display === 'block') return; + + let { + type = 'points-3', + trackLine = false, + startClick = true, + startMouseDown = false, + } = option; + + this.drawType = type; + this.trackLine = trackLine; + this.startClick = startClick; + this.startMouseDown = startMouseDown; + // this.toggle(true); + this.callback = callback; + this.onChange = onChange; + + this.canvas.style.display = 'block'; + this.points = []; + + this.clear(); + } + + end() { + this.canvas.style.display = 'none'; + } + + setStyle() { + let context = this.context; + context.setLineDash([]); + context.lineWidth = 1; + + context.strokeStyle = '#fcff4b'; + context.fillStyle = '#fcff4b'; + + // switch (this.drawType) { + // case 'points-3': + // context.strokeStyle = '#fcff4b'; + // context.fillStyle = '#fcff4b'; + // break; + // case 'box': + // context.strokeStyle = '#fcff4b'; + // context.fillStyle = '#fcff4b'; + // break; + // } + } + + clear() { + let context = this.context; + + if ( + this.canvas.width !== this.canvas.clientWidth || + this.canvas.height !== this.canvas.clientHeight + ) { + this.canvas.width = this.canvas.clientWidth; + this.canvas.height = this.canvas.clientHeight; + } + + context.clearRect(0, 0, this.canvas.width, this.canvas.height); + } + + drawTrackLine(pos: Point) { + let context = this.context; + let { width, height } = this.canvas; + + // context.beginPath(); + context.fillStyle = 'red'; + context.strokeStyle = 'red'; + context.lineWidth = 1; + context.beginPath(); + context.setLineDash([2, 2]); + + context.moveTo(0, pos.y); + context.lineTo(width, pos.y); + + context.moveTo(pos.x, 0); + context.lineTo(pos.x, height); + context.stroke(); + + context.setLineDash([]); + } + + drawPoint3(pos: Point) { + let context = this.context; + + this.clear(); + + if (this.trackLine) this.drawTrackLine(pos); + + context.beginPath(); + this.setStyle(); + + this.points.forEach((p, index) => { + if (index === 0) context.moveTo(p.x, p.y); + else context.lineTo(p.x, p.y); + }); + + if (this.points.length > 0) context.lineTo(pos.x, pos.y); + else { + this.drawPointer(pos); + } + + context.stroke(); + } + + drawBox(pos: Point) { + let context = this.context; + + this.clear(); + if (this.trackLine) this.drawTrackLine(pos); + + context.beginPath(); + this.setStyle(); + + if (this.points.length > 0) { + let point = this.points[0]; + this.context.strokeRect(pos.x, pos.y, point.x - pos.x, point.y - pos.y); + } else { + this.drawPointer(pos); + } + + context.stroke(); + } + + drawBox2(pos: Point) { + let context = this.context; + + this.clear(); + if (this.trackLine) this.drawTrackLine(pos); + + context.beginPath(); + this.setStyle(); + let len = this.points.length; + if (len > 0) { + if (len >= 2) { + let p1 = this.points[0]; + let p2 = this.points[1]; + this.context.strokeRect(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y); + } + + if (len === 1 || len === 3) { + let point = len === 1 ? this.points[0] : this.points[2]; + this.context.strokeRect(pos.x, pos.y, point.x - pos.x, point.y - pos.y); + } + } + + if (len === 0 || len === 2) { + this.drawPointer(pos); + } + + context.stroke(); + } + + drawPointer(pos: Point) { + this.context.fillRect(pos.x - 3, pos.y - 3, 6, 6); + } + + onMouseMove(event: MouseEvent) { + event.stopPropagation(); + let pos = { x: event.offsetX, y: event.offsetY }; + switch (this.drawType) { + case 'points-1': + case 'points-3': + // console.time('test-3'); + + this.drawPoint3(pos); + // console.timeEnd('test-3'); + + break; + case 'box': + // console.time('test-box'); + + this.drawBox(pos); + // console.timeEnd('test-box'); + + break; + case 'box-2': + // console.time('test-box'); + + this.drawBox2(pos); + // console.timeEnd('test-box'); + + break; + } + } + + handleCallback() { + if (this.callback) { + this.callback(this.points); + } + } + + onMouseUp(event: MouseEvent) { + event.stopPropagation(); + if (!this.enabled || !this.startMouseDown) return; + this.handleMouse(event); + } + + onMouseDown(event: MouseEvent) { + event.stopPropagation(); + if (!this.enabled || !this.startMouseDown) return; + this.handleMouse(event); + } + + onClick(event: MouseEvent) { + event.stopPropagation(); + if (!this.enabled || !this.startClick) return; + this.handleMouse(event); + } + + handleMouse(event: MouseEvent) { + this.points.push({ x: event.offsetX, y: event.offsetY }); + + if (this.onChange) this.onChange(this.points); + + if (this.points.length >= TypePoints[this.drawType]) { + this.end(); + this.handleCallback(); + } + } +} diff --git a/frontend/text-tool/src/packages/pc-render/action/Edit2DAction.ts b/frontend/text-tool/src/packages/pc-render/action/Edit2DAction.ts new file mode 100644 index 00000000..db8a4f4d --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/action/Edit2DAction.ts @@ -0,0 +1,221 @@ +import * as THREE from 'three'; +import Image2DRenderView from '../renderView/Image2DRenderView'; +import Action from './Action'; +import { ITransform } from '../type'; +import { Object2D, Rect, Box2D, Vector2Of4 } from '../objects'; +import { Event } from '../config'; +import { get } from '../utils/tempVar'; +import { Info, RectTool, Box3DTool, ClearHandler, IBoxEvent, IRectEvent } from '../common/BasicSvg'; +import { isLeft, isRight } from '../utils'; +import * as _ from 'lodash'; + +let tempV2_1 = new THREE.Vector2(); +let tempV2_2 = new THREE.Vector2(); + +export default class Edit2DAction extends Action { + static actionName: string = 'edit-2d'; + renderView: Image2DRenderView; + object: Object2D | null = null; + rectTool: RectTool; + boxTool: Box3DTool; + // + clearCall: ClearHandler[] = []; + + constructor(renderView: Image2DRenderView) { + super(); + this.renderView = renderView; + this.rectTool = new RectTool(renderView.container); + this.boxTool = new Box3DTool(renderView.container); + this.rectTool.setOption({ + rotateAble: false, + circleStyle: { + opacity: 1, + }, + }); + this.boxTool.setOption({ + circleStyle: { + opacity: 1, + }, + }); + } + + init() { + this.initRectDrop(); + this.initBoxDrop(); + this.renderView.addEventListener(Event.RENDER_AFTER, () => { + this.update(); + }); + + this.renderView.pointCloud.addEventListener(Event.SELECT, () => { + let selection = this.renderView.pointCloud.selection; + if (selection.length === 1 && selection[0] instanceof Object2D) { + this.object = selection[0]; + } else { + this.object = null; + } + }); + } + + destroy() { + this.clearCall.forEach((fn) => fn()); + this.rectTool.destroy(); + this.boxTool.destroy(); + } + + initBoxDrop() { + // let scaleSize = new THREE.Vector2(); + let renderView = this.renderView; + // const onStart = (data: IBoxEvent) => { + // let { imgSize, width, height } = renderView; + // scaleSize.set(imgSize.x / width, imgSize.y / height); + // }; + const onUpdate = _.throttle((data: IBoxEvent) => { + // console.log('drop', data); + if (!data.data) return; + const { + dir, + info, + data: { positions1, positions2 }, + } = data; + if (!positions1 || !positions2) return; + if (dir === 'front' || info === 'front-bg') { + let posMap: Record = {}; + positions1.reduce( + (map: Record, pos: THREE.Vector2, index: number) => { + let copy = pos.clone(); + renderView.domToImg(copy); + map[index] = copy; + return map; + }, + posMap, + ); + this.updateBox2DData('positions1', posMap); + } else if (dir === 'back' || info === 'back-bg') { + let posMap: Record = {}; + positions2.reduce( + (map: Record, pos: THREE.Vector2, index: number) => { + let copy = pos.clone(); + renderView.domToImg(copy); + map[index] = copy; + return map; + }, + posMap, + ); + this.updateBox2DData('positions2', posMap); + } + }, 30); + // this.boxTool.addEventListener('start', onStart); + this.boxTool.addEventListener('update', onUpdate); + + // this.boxTool.box2DWrap.addEventListener('dblclick', (e: MouseEvent) => { + // if (this.object) { + // e.stopPropagation(); + + // this.renderView.pointCloud.dispatchEvent({ + // type: Event.OBJECT_DBLCLICK, + // data: this.object, + // }); + // } + // }); + } + + validRect(center: THREE.Vector2, size: THREE.Vector2, moveOnly: boolean) {} + + initRectDrop() { + // const scaleSize = new THREE.Vector2(); + + // const onStart = (data: IRectEvent) => { + // let { imgSize, width, height } = this.renderView; + // scaleSize.set(imgSize.x / width, imgSize.y / height); + // }; + const onUpdate = _.throttle((data: IRectEvent) => { + if (!data.data) return; + const { center, size } = data.data; + if (!center || !size) return; + + // let { imgSize, width, height } = this.renderView; + let scale = this.renderView.getScale(); + let newCenter = center.clone(); + let newScale = size.clone().multiplyScalar(1 / scale); + this.renderView.domToImg(newCenter); + this.validRect(newCenter, newScale, data.info === 'bg'); + + this.updateRectData(newCenter, newScale); + }, 30); + + // this.rectTool.addEventListener('start', onStart); + this.rectTool.addEventListener('update', onUpdate); + + // this.rectTool.rectWrap.addEventListener('dblclick', () => { + // if (this.object) { + // this.renderView.pointCloud.dispatchEvent({ + // type: Event.OBJECT_DBLCLICK, + // data: this.object, + // }); + // } + // }); + } + update() { + if ( + !this.isEnable() || + !this.object || + !this.renderView.isRenderable(this.object as Object2D) + ) { + this.rectTool.hide(); + this.boxTool.hide(); + return; + } + if (this.object instanceof Rect) { + this.rectTool.show(); + this.boxTool.hide(); + this.updateRect(); + } else { + this.boxTool.show(); + this.rectTool.hide(); + this.updateBox2D(); + } + } + updateBox2D() { + let newPos = get(THREE.Vector2, 1); + let { positions1, positions2 } = this.object as Box2D; + // let { imgSize, width, height } = this.renderView; + // const scaleSize = get(THREE.Vector2, 2).set(width, height).divide(imgSize); + const circlePositions1: Vector2Of4 = this.boxTool.getEmptyVector2Of4(); + const circlePositions2: Vector2Of4 = this.boxTool.getEmptyVector2Of4(); + positions1.forEach((pos, index) => { + newPos.copy(pos); + this.renderView.imgToDom(newPos); + circlePositions1[index].copy(newPos); + }); + positions2.forEach((pos, index) => { + newPos.copy(pos); + this.renderView.imgToDom(newPos); + circlePositions2[index].copy(newPos); + }); + this.boxTool.updateBox2D(circlePositions1, circlePositions2); + } + + updateRect() { + let { center, size } = this.object as Rect; + let scale = this.renderView.getScale(); + let newCenter = tempV2_1.copy(center); + let newSize = tempV2_2.copy(size).multiplyScalar(scale); + + this.renderView.imgToDom(newCenter); + this.rectTool.updateRect(newSize, newCenter); + } + + updateRectData(center: THREE.Vector2, size?: THREE.Vector2) { + let object = this.object as Rect; + this.renderView.pointCloud.update2DRect(object, { center, size }); + } + updateBox2DData( + positionName: 'positions1' | 'positions2', + positionMap: Record, + ) { + let object = this.object as Box2D; + let option = {} as any; + option[positionName] = positionMap; + this.renderView.pointCloud.update2DBox(object, option); + } +} diff --git a/frontend/text-tool/src/packages/pc-render/action/LabelAction.ts b/frontend/text-tool/src/packages/pc-render/action/LabelAction.ts new file mode 100644 index 00000000..b0b324f2 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/action/LabelAction.ts @@ -0,0 +1,121 @@ +import * as THREE from 'three'; +import MainRenderView from '../renderView/MainRenderView'; +import { Event } from '../config'; +import Action from './Action'; + +interface Point { + x: number; + y: number; +} + +export default class LabelAction extends Action { + static actionName: string = 'label'; + renderView: MainRenderView; + canvas: HTMLCanvasElement; + context: CanvasRenderingContext2D; + constructor(renderView: MainRenderView) { + super(); + + this.renderView = renderView; + + let canvas = document.createElement('canvas'); + canvas.className = 'label'; + canvas.style.position = 'absolute'; + canvas.style.left = '0px'; + canvas.style.top = '0px'; + canvas.style.width = '100%'; + canvas.style.height = '100%'; + canvas.style.pointerEvents = 'none'; + // canvas.style.display = 'none'; + // canvas.style.cursor = 'crosshair'; + + let context = canvas.getContext('2d') as CanvasRenderingContext2D; + + this.canvas = canvas; + this.context = context; + + this.onRender = this.onRender.bind(this); + // this.onMouseMove = this.onMouseMove.bind(this); + // this.toggle(false); + } + + init() { + let dom = this.renderView.container; + dom.appendChild(this.canvas); + + this.renderView.addEventListener(Event.RENDER_AFTER, this.onRender); + + // this.start(); + } + destroy() { + this.renderView.removeEventListener(Event.RENDER_AFTER, this.onRender); + this.renderView.container.removeChild(this.canvas); + } + + onRender() { + let { camera, pointCloud, width, height } = this.renderView; + let context = this.context; + + this.canvas.width = this.canvas.clientWidth; + this.canvas.height = this.canvas.clientHeight; + + context.textAlign = 'center'; + context.font = '12px'; + // context.fillStyle = '#FF0000'; + context.strokeStyle = '#FF0000'; + + // let ctx = context; + // ctx.font = '20px Georgia'; + // ctx.fillText('Hello World!', 100, 50); + + // return; + + let matrix = new THREE.Matrix4(); + matrix.copy(camera.projectionMatrix); + matrix.multiply(camera.matrixWorldInverse); + + let objects = pointCloud.getAnnotate3D(); + + let list = []; + let pos = new THREE.Vector3(); + let pos1 = new THREE.Vector3(); + + context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + objects.forEach((e) => { + if (!e.visible) return; + let userData = e.userData; + let classType = userData.classType || ''; + + pos.set(0, 0, 0); + pos.applyMatrix4(e.matrixWorld); + pos.applyMatrix4(matrix); + pos.x = ((pos.x + 1) / 2) * width; + pos.y = (-(pos.y - 1) / 2) * height; + pos.z = 0; + + pos1.set(1, 0, 0); + pos1.applyMatrix4(e.matrixWorld); + pos1.applyMatrix4(matrix); + pos1.x = ((pos1.x + 1) / 2) * width; + pos1.y = (-(pos1.y - 1) / 2) * height; + pos1.z = 0; + + let length = pos1.sub(pos).length(); + length = length / 60; + let scale = Math.max(0.5, Math.min(1, length)); + + let subId = userData.id.slice(-4); + let name = classType ? `${subId}(${classType})` : subId; + // debugger; + // context.fillText(name, pos.x, pos.y); + // let info = context.measureText(name); + // let sizeH = 12; + // let sizeW = info.width; + // context.fillRect(pos.x - sizeW / 2, pos.y - sizeH / 2, sizeW, sizeH); + context.strokeText(name, pos.x, pos.y); + // let obj = { name: : subId, x: pos.x, y: pos.y, scale }; + // list.push(obj); + }); + } +} diff --git a/frontend/text-tool/src/packages/pc-render/action/OrbitControlsAction.ts b/frontend/text-tool/src/packages/pc-render/action/OrbitControlsAction.ts new file mode 100644 index 00000000..dcc614c0 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/action/OrbitControlsAction.ts @@ -0,0 +1,67 @@ +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; +import MainRenderView from '../renderView/MainRenderView'; +import * as THREE from 'three'; +import Action from './Action'; +import { Event } from '../config'; +import * as _ from 'lodash'; + +export default class OrbitControlsAction extends Action { + static actionName: string = 'orbit-control'; + renderView: MainRenderView; + control: OrbitControls; + constructor(renderView: MainRenderView) { + super(); + + this.renderView = renderView; + + this.control = new OrbitControls(renderView.camera, renderView.renderer.domElement); + this.control.maxDistance = 1000; + this.control.minDistance = 10; + // this.control.autoRotate = false; + // this.control.update(); + + this.control.addEventListener('change', this.controlChange.bind(this)); + + // this.selectChange = this.selectChange.bind(this); + // this.transformChange = this.transformChange.bind(this); + // this.focus = _.throttle(this.focus.bind(this), 100); + } + + init(): void { + // this.renderView.pointCloud.addEventListener(Event.SELECT, this.selectChange); + // this.renderView.pointCloud.addEventListener(Event.OBJECT_TRANSFORM, this.transformChange); + } + + transformChange(data: any) { + let object = data.object as THREE.Object3D; + this.focus(object.position); + } + + selectChange() { + let { selection } = this.renderView.pointCloud; + if (selection.length === 1 || selection[0] instanceof THREE.Object3D) { + let object = selection[0] as THREE.Object3D; + this.focus(object.position); + } else { + this.focus(); + } + } + + focus(pos?: THREE.Vector3) { + if (pos) { + this.control.target.copy(pos); + } else { + this.control.target.set(0, 0, 0); + } + + // this.control.update(); + } + + toggle(enabled: boolean) { + this.control.enabled = enabled; + } + + controlChange() { + this.renderView.render(); + } +} diff --git a/frontend/text-tool/src/packages/pc-render/action/Render2DAction.ts b/frontend/text-tool/src/packages/pc-render/action/Render2DAction.ts new file mode 100644 index 00000000..f65ec99a --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/action/Render2DAction.ts @@ -0,0 +1,70 @@ +import * as THREE from 'three'; +import Image2DRenderView from '../renderView/Image2DRenderView'; +import { Event } from '../config'; +import Action from './Action'; +import { Object2D, Rect, Box2D } from '../objects'; +import { renderBox2D, renderRect } from '../utils'; + +export default class Render2DAction extends Action { + static actionName: string = 'render-2d-shape'; + renderView: Image2DRenderView; + constructor(renderView: Image2DRenderView) { + super(); + + this.renderView = renderView; + this.onRender = this.onRender.bind(this); + } + + init() { + this.renderView.addEventListener(Event.RENDER_AFTER, this.onRender); + } + destroy() { + this.renderView.removeEventListener(Event.RENDER_AFTER, this.onRender); + } + + renderRect(obj: Rect, lineWidth: number) { + let pointCloud = this.renderView.pointCloud; + let { selectionMap } = pointCloud; + let { context } = this.renderView.proxy; + let selectColor = `#${pointCloud.selectColor.getHexString()}`; + let color = selectionMap[obj.uuid] ? selectColor : obj.color; + let highFlag = this.renderView.isHighlight(obj); + color = highFlag ? selectColor : color; + + renderRect(context, obj, { lineWidth: lineWidth, color }); + } + + renderBox2D(obj: Box2D, lineWidth: number) { + let pointCloud = this.renderView.pointCloud; + let { selectionMap } = pointCloud; + let { context } = this.renderView.proxy; + let selectColor = `#${pointCloud.selectColor.getHexString()}`; + let color = selectionMap[obj.uuid] ? selectColor : obj.color; + let highFlag = this.renderView.isHighlight(obj); + color = highFlag ? selectColor : color; + + renderBox2D(context, obj, { lineWidth: lineWidth, color }); + } + + getLineWidth() { + let size = 1 / this.renderView.getScale(); + return size; + // return Math.min(2, Math.max(0.5, size)); + } + + onRender() { + let objects = this.renderView.get2DObject(); + let lineWidth = this.getLineWidth(); + + this.renderView.setContextTransform(); + objects.forEach((obj) => { + if (this.renderView.isRenderable(obj)) { + if (obj instanceof Rect) { + this.renderRect(obj, lineWidth); + } else { + this.renderBox2D(obj as Box2D, lineWidth); + } + } + }); + } +} diff --git a/frontend/text-tool/src/packages/pc-render/action/ResizeTransAction.ts b/frontend/text-tool/src/packages/pc-render/action/ResizeTransAction.ts new file mode 100644 index 00000000..050b7ece --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/action/ResizeTransAction.ts @@ -0,0 +1,396 @@ +import * as THREE from 'three'; +import SideRenderView, { axisUpInfo } from '../renderView/SideRenderView'; +import Action from './Action'; +import { ITransform, AnnotateType } from '../type'; +import { Event } from '../config'; +import { Box } from '../objects'; +import { IRectEvent, RectTool, ClearHandler } from '../common/BasicSvg'; + +// type DragHandler = (offsetLocal: THREE.Vector3, offsetCamera: THREE.Vector2) => ITransform | null; +// type ClearHandler = () => void; + +const tempV2_1 = new THREE.Vector2(); +const tempV2_2 = new THREE.Vector2(); + +const tempV3_1 = new THREE.Vector3(); +const tempV3_2 = new THREE.Vector3(); +const tempV3_3 = new THREE.Vector3(); + +const tempM4_1 = new THREE.Matrix4(); +const tempM4_2 = new THREE.Matrix4(); + +interface IEditConfig { + transform: boolean; + move: boolean; + moveCanvas: boolean; + zoom: boolean; +} + +export default class ResizeTransAction extends Action { + static actionName: string = 'resize-translate'; + // + // rotatable: boolean = true; + isRotating: boolean = false; + rotation: number = 0; + rectTool: RectTool = {} as RectTool; + clearCall: ClearHandler[] = []; + editConfig: IEditConfig = { + transform: true, + move: true, + zoom: true, + moveCanvas: true, + }; + + renderView: SideRenderView; + constructor(renderView: SideRenderView) { + super(); + this.renderView = renderView; + this.rectTool = new RectTool(renderView.container); + this.rectTool.setOption({ + lineStyle: { + stroke: '#ffffff', + 'stroke-dasharray': 0, + }, + rotateStyle: { + fill: 'rgba(204,204,204,0.63)', + stroke: 'rgba(204,204,204,0.63)', + }, + }); + } + init() { + this.initDragResize(); + this.initRotate(); + this.initEvent(); + this.initContainerEvent(); + } + + destroy() { + this.clearCall.forEach((fn) => fn()); + } + + initEvent() { + this.renderView.addEventListener(Event.RENDER_AFTER, () => { + // this.updateDom(); + if (!this.isRotating) { + this.updateDom(); + } else { + this.rectTool.updateRotation(); + } + }); + } + + initDragResize() { + let { container } = this.renderView; + const _position = new THREE.Vector3(); + const _scale = new THREE.Vector3(); + + const onUpdate = (e: IRectEvent) => { + const info = e.info; + const { object } = this.renderView; + const { center, size } = e.data || {}; + if (!center || !size || !object) return; + if (info === 'bg') { + if (!this.editConfig.move) return; + } else if (!this.editConfig.transform || !object.editConfig.resize) return; + + this.renderView.needFit = false; + const { left, right } = this.renderView.camera; + const scaleSize = (right - left) / this.renderView.container.clientWidth; + const { scale } = object; + + // center.multiplyScalar(scaleSize) + size.multiplyScalar(scaleSize); + center.set( + (center.x / container.clientWidth) * 2 - 1, + -(center.y / container.clientHeight) * 2 + 1, + ); + + _position.set(center.x, center.y, 0); + _position.unproject(this.renderView.camera); + _scale.set(size.x, size.y, 0); + this.translateToLocalV(_scale); + let axis = this.renderView.axis; + switch (axis) { + case 'z': + _scale.set(Math.abs(_scale.x), Math.abs(_scale.y), scale.z); + break; + case 'x': + case '-x': + _scale.set(scale.x, Math.abs(_scale.y), Math.abs(_scale.z)); + break; + case 'y': + case '-y': + _scale.set(Math.abs(_scale.x), scale.y, Math.abs(_scale.z)); + break; + } + this.updateChange( + { + position: _position, + scale: _scale, + }, + info === 'bg' + ? 'move' + : ['rt', 'rb', 'lb', 'lt'].includes(info || '') + ? 'corner' + : 'side', + ); + }; + const onEnd = (event: IRectEvent) => { + this.updateEnd(); + this.renderView.needFit = true; + this.onResizeEnd(event); + }; + this.rectTool.addEventListener('start', this.onResizeStart); + this.rectTool.addEventListener('update', onUpdate); + this.rectTool.addEventListener('end', onEnd); + } + onResizeEnd(event: IRectEvent) {} + onRotateEnd() {} + onResizeStart() {} + + isLeft(e: MouseEvent) { + return e.button === 0; + } + + isRight(e: MouseEvent) { + return e.button === 2; + } + updateDom() { + const { + object, + axis, + camera: { left, right }, + } = this.renderView; + if (!this.isEnable() || !object) { + this.rectTool.hide(); + this.renderView.container.style.cursor = 'default'; + return; + } + + const scaleSize = (right - left) / this.renderView.container.clientWidth; + let scale = object.scale.clone().divideScalar(scaleSize); + this.renderView.container.style.cursor = 'grab'; + // debugger + let rightTop = this.renderView.projectRect.max.clone(); + let leftBottom = this.renderView.projectRect.min.clone(); + + rightTop = this.renderView.cameraToCanvas(rightTop); + leftBottom = this.renderView.cameraToCanvas(leftBottom); + + const center = tempV2_1; + const size = tempV2_2; + // console.log(object.scale.x,object.scale.y,object.scale.z) + center.set((leftBottom.x + rightTop.x) / 2, (leftBottom.y + rightTop.y) / 2); + switch (axis) { + case '-x': + case 'x': + size.set(scale.y, scale.z); + break; + case 'y': + case '-y': + size.set(scale.x, scale.z); + break; + case 'z': + size.set(scale.y, scale.x); + break; + } + + // size.set(Math.abs(leftBottom.x - rightTop.x), Math.abs(leftBottom.y - rightTop.y)); + this.rectTool.show(); + this.rectTool.updateRect(size, center); + // this.rectTool.setOption({ rotatable: this.rotatable }); + + // this.updateRotation(); + } + updateRotation() { + const object = this.renderView.object; + if (!object) return; + tempV3_2.setFromMatrixColumn(this.renderView.camera.matrix, 1); + switch (this.renderView.axis) { + case '-x': + case 'x': + tempV3_3.set(0, 0, 1); + break; + case 'y': + case '-y': + tempV3_3.set(0, 0, 1); + break; + case 'z': + tempV3_3.set(1, 0, 0); + break; + } + tempV3_3.applyQuaternion(object.quaternion); + + let rotation = (tempV3_2.angleTo(tempV3_3) * 180) / Math.PI; + if (this.rotation > 0) { + rotation *= -1; + } + this.rectTool.rotation = rotation; + this.rectTool.updateRotation(); + } + updateChange(data: ITransform | null, type: 'move' | 'side' | 'corner' | 'rotation') { + if (!data) return; + this.renderView.pointCloud.updateObjectTransform(this.renderView.object as any, data); + this.renderView.updateProjectRect(); + } + render() { + this.renderView.pointCloud.render(); + } + updateEnd() { + if (this.renderView.enableFit) { + this.renderView.fitObject(); + } + this.render(); + } + + initContainerEvent() { + let _this = this; + let dom = this.renderView.container; + let last = new THREE.Vector2(); + let offset = new THREE.Vector2(); + let offsetV3 = new THREE.Vector3(); + let tempV3 = new THREE.Vector3(); + let renderView = this.renderView; + let camera = renderView.camera; + + dom.style.cursor = 'grab'; + dom.addEventListener('mousedown', onmousedown); + dom.addEventListener('wheel', onmousewheel); + function onmousedown(e: MouseEvent) { + if (!_this.isEnable() || _this.isLeft(e) || !_this.editConfig.moveCanvas) return; + e.preventDefault(); + e.stopPropagation(); + // console.log('move'); + + last.set(e.clientX, e.clientY); + + document.addEventListener('mousemove', onDocMove); + document.addEventListener('mouseup', onDocUp); + + function onDocMove(e: MouseEvent) { + let { right, left } = camera; + + offset.set(e.clientX, e.clientY).sub(last); + offset.multiplyScalar((right - left) / renderView.container.clientWidth); + offset.y *= -1; + + offsetV3 + .set(offset.x, offset.y, 0) + .applyMatrix4(camera.matrixWorld) + .sub(tempV3.set(0, 0, 0).applyMatrix4(camera.matrixWorld)); + // console.log(offset, offsetV3); + renderView.camera.position.sub(offsetV3); + renderView.enableFit = false; + renderView.camera.updateMatrixWorld(); + _this.render(); + + last.set(e.clientX, e.clientY); + } + function onDocUp() { + renderView.enableFit = true; + document.removeEventListener('mousemove', onDocMove); + document.removeEventListener('mouseup', onDocUp); + } + } + function onmousewheel(e: WheelEvent) { + e.preventDefault(); + e.stopPropagation(); + + let maxZoom = 10; + let minZoom = 0.2; + if (e.deltaY === 0 || !_this.editConfig.zoom) return; + if (e.deltaY > 0) { + renderView.zoom = renderView.zoom * 1.05; + } else { + renderView.zoom = renderView.zoom * 0.95; + } + + if (renderView.zoom > maxZoom) renderView.zoom = maxZoom; + if (renderView.zoom < minZoom) renderView.zoom = minZoom; + + renderView.updateCameraProject(); + // renderView.needFit = false; + _this.render(); + // renderView.fitObject(); + } + } + + initRotate() { + let starQuat = new THREE.Quaternion(); + let tempQuat = new THREE.Quaternion(); + let tempEuler = new THREE.Euler(); + // let div = document.createElement('div'); + const onStart = (e: IRectEvent) => { + const { object } = this.renderView; + if (!object) return; + starQuat.setFromEuler(object.rotation); + this.renderView.needFit = false; + }; + const onRotate = (e: IRectEvent) => { + const data = e.data; + const { object } = this.renderView; + if (!data || !object || !this.editConfig.transform) return; + const { offsetAngle } = data; + if (offsetAngle === undefined) return; + this.rotation = offsetAngle; + this.isRotating = true; + let axisDir = tempV3_1.set(0, 0, 0); + let axis = this.renderView.axis; + switch (axis) { + case 'z': + axisDir.z = 1; + break; + case 'x': + axisDir.x = 1; + break; + case '-x': + axisDir.x = -1; + break; + case 'y': + axisDir.y = 1; + break; + case '-y': + axisDir.y = -1; + break; + } + tempQuat.setFromAxisAngle(axisDir, offsetAngle).premultiply(starQuat); + tempEuler.setFromQuaternion(tempQuat); + this.updateChange({ rotation: tempEuler.clone() }, 'rotation'); + }; + const onEnd = () => { + this.renderView.enableFit = true; + this.renderView.needFit = true; + this.isRotating = false; + this.rectTool.rotation = 0; + this.rectTool.updateRotation(); + this.updateEnd(); + this.onRotateEnd(); + }; + this.rectTool.addEventListener('start-rotate', onStart); + this.rectTool.addEventListener('rotate', onRotate); + this.rectTool.addEventListener('end-rotate', onEnd); + } + translateToLocalV(offset: THREE.Vector3) { + // console.log('start',offset) + let matrix = tempM4_1 + .copy(this.renderView.object?.matrixWorld as any) + .invert() + .multiply(this.renderView.camera.matrixWorld); + let center = tempV3_1.set(0, 0, 0).applyMatrix4(tempM4_1); + offset + .applyMatrix4(matrix) + .sub(center) + .multiply(this.renderView.object?.scale as any); + // console.log('end',offset) + } + translateToViewV(offset: THREE.Vector3) { + // console.log('start',offset) + let matrix = tempM4_1 + .copy(this.renderView.object?.matrixWorld as any) + .multiply(this.renderView.camera.matrix); + let center = tempV3_3.set(0, 0, 0).applyMatrix4(tempM4_1); + offset.applyMatrix4(matrix).sub(center); + + // console.log('end',offset) + } +} diff --git a/frontend/text-tool/src/packages/pc-render/action/SelectAction.ts b/frontend/text-tool/src/packages/pc-render/action/SelectAction.ts new file mode 100644 index 00000000..d5b9dafb --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/action/SelectAction.ts @@ -0,0 +1,165 @@ +import * as THREE from 'three'; +import MainRenderView from '../renderView/MainRenderView'; +import Image2DRenderView from '../renderView/Image2DRenderView'; +import { Rect, Box2D, Object2D, AnnotateObject } from '../objects'; +import { Event } from '../config'; +import Action from './Action'; +import { get } from '../utils/tempVar'; +import * as _ from 'lodash'; + +export default class SelectAction extends Action { + static actionName: string = 'select'; + renderView: MainRenderView | Image2DRenderView; + + private _time: number = 0; + private _mouseDown: boolean = false; + private _clickValid: boolean = false; + private _mouseDownPos: THREE.Vector2 = new THREE.Vector2(); + private raycaster: THREE.Raycaster = new THREE.Raycaster(); + + constructor(renderView: MainRenderView | Image2DRenderView) { + super(); + this.renderView = renderView; + this.enabled = true; + + this.onMouseDown = this.onMouseDown.bind(this); + this.onMouseUp = this.onMouseUp.bind(this); + // this.onDBLClick = this.onDBLClick.bind(this); + this.onClick = _.debounce(this.onClick.bind(this), 500, { leading: true, trailing: false }); + } + init() { + let dom = this.renderView.container; + this._mouseDown = false; + this._mouseDownPos = new THREE.Vector2(); + + dom.addEventListener('mousedown', this.onMouseDown); + dom.addEventListener('mouseup', this.onMouseUp); + // dom.addEventListener('dblclick', this.onDBLClick); + dom.addEventListener('click', this.onClick); + } + + onDBLClick(event: MouseEvent) { + let object = this.getObject(event); + if (object) { + event.stopPropagation(); + + this.renderView.pointCloud.dispatchEvent({ + type: Event.OBJECT_DBLCLICK, + data: object, + }); + } + // console.log('onDBLClick'); + } + onSelect() {} + onClick(event: MouseEvent) { + if (!this.enabled || !this._clickValid) return; + + // console.log('onClick'); + let object = this.getObject(event); + if (object) { + this.selectObject(object as any); + this.onSelect(); + } + } + + onMouseDown(event: MouseEvent) { + if (!this.enabled) return; + + this._mouseDown = true; + this._mouseDownPos.set(event.offsetX, event.offsetY); + } + onMouseUp(event: MouseEvent) { + if (!this.enabled) return; + + let tempVec2 = new THREE.Vector2(); + let distance = tempVec2.set(event.offsetX, event.offsetY).distanceTo(this._mouseDownPos); + this._clickValid = this._mouseDown && distance < 10; + this._mouseDown = false; + } + + getObject(event: MouseEvent) { + let object; + if (this.renderView instanceof MainRenderView) { + object = this.checkMainView(event); + } else { + object = this.checkImage2DView(event); + } + + return object; + } + + checkMainView(event: MouseEvent) { + let pos = get(THREE.Vector2, 0); + this.getProjectPos(event, pos); + let annotate3D = this.renderView.pointCloud.getAnnotate3D(); + + this.raycaster.setFromCamera(pos, this.renderView.camera); + const intersects = this.raycaster.intersectObjects(annotate3D); + // console.log(intersects); + if (intersects.length > 0) { + return intersects[0].object; + // this.selectObject(intersects[0].object as any); + } + } + + checkImage2DView(event: MouseEvent) { + // debugger; + let renderView = this.renderView as Image2DRenderView; + let imgSize = renderView.imgSize; + + let findObject; + let imgPos = get(THREE.Vector2, 0).set(event.offsetX, event.offsetY); + // 转换到图片坐标系 + renderView.domToImg(imgPos); + // tempPos.x = ((pos.x + 1) / 2) * imgSize.x; + // tempPos.y = ((-pos.y + 1) / 2) * imgSize.y; + + if (!findObject && (renderView.renderRect || renderView.renderBox2D)) { + let annotate2D = renderView.get2DObject(); + let obj; + for (let i = annotate2D.length - 1; i >= 0; i--) { + obj = annotate2D[i]; + + if (renderView.isRenderable(obj) && obj.isContainPosition(imgPos)) { + findObject = obj; + break; + } + } + } + + if (!findObject && renderView.renderBox) { + let annotate3D = renderView.get3DObject(); + let projectPos = get(THREE.Vector2, 1).copy(imgPos); + this.getProjectImgPos(projectPos); + this.raycaster.setFromCamera(projectPos, this.renderView.camera); + + let intersects = this.raycaster.intersectObjects(annotate3D); + findObject = intersects.length > 0 ? intersects[0].object : null; + } + + return findObject; + } + + selectObject(object?: AnnotateObject) { + this.renderView.pointCloud.selectObject(object); + } + + getProjectImgPos(pos: THREE.Vector2, target?: THREE.Vector2) { + let renderView = this.renderView as Image2DRenderView; + let { imgSize } = renderView; + + target = target || pos; + target.x = (pos.x / imgSize.x) * 2 - 1; + target.y = (-pos.y / imgSize.y) * 2 + 1; + return target; + } + + getProjectPos(event: MouseEvent, pos?: THREE.Vector2) { + let x = (event.offsetX / this.renderView.width) * 2 - 1; + let y = (-event.offsetY / this.renderView.height) * 2 + 1; + + pos = pos || new THREE.Vector2(); + pos.set(x, y); + return pos; + } +} diff --git a/frontend/text-tool/src/packages/pc-render/action/SelectAction2D.ts b/frontend/text-tool/src/packages/pc-render/action/SelectAction2D.ts new file mode 100644 index 00000000..4d9031b7 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/action/SelectAction2D.ts @@ -0,0 +1,190 @@ +// import * as THREE from 'three'; +// import Image2DRenderView from '../renderView/Image2DRenderView'; +// import Action from './Action'; +// import { Event } from '../config/index'; +// import { get } from '../utils/tempVar'; +// import Box from '../objects/Box'; + +// type IFace = 'front' | 'back' | 'left' | 'right' | 'top' | 'bottom'; +// /** +// * front +X axis,right +Y axis,up +Z axis +// */ +// let faces: IFace[] = ['front', 'back', 'left', 'right', 'top', 'bottom']; +// function getRect( +// v1: THREE.Vector3, +// v2: THREE.Vector3, +// v3: THREE.Vector3, +// v4: THREE.Vector3, +// box: THREE.Box3, +// face: IFace, +// ) { +// switch (face) { +// case 'front': +// v1.set(box.max.x, box.min.y, box.max.z); +// v2.set(box.max.x, box.min.y, box.min.z); +// v3.set(box.max.x, box.max.y, box.min.z); +// v4.set(box.max.x, box.max.y, box.max.z); +// break; +// case 'back': +// v1.set(box.min.x, box.min.y, box.max.z); +// v2.set(box.min.x, box.min.y, box.min.z); +// v3.set(box.min.x, box.max.y, box.min.z); +// v4.set(box.min.x, box.max.y, box.max.z); +// break; +// case 'left': +// v1.set(box.max.x, box.min.y, box.max.z); +// v2.set(box.max.x, box.min.y, box.min.z); +// v3.set(box.min.x, box.min.y, box.min.z); +// v4.set(box.min.x, box.min.y, box.max.z); +// break; +// case 'right': +// v1.set(box.max.x, box.max.y, box.max.z); +// v2.set(box.max.x, box.max.y, box.min.z); +// v3.set(box.min.x, box.max.y, box.min.z); +// v4.set(box.min.x, box.max.y, box.max.z); +// break; +// case 'top': +// v1.set(box.max.x, box.min.y, box.max.z); +// v2.set(box.max.x, box.max.y, box.max.z); +// v3.set(box.min.x, box.max.y, box.max.z); +// v4.set(box.min.x, box.min.y, box.max.z); +// break; +// case 'bottom': +// v1.set(box.max.x, box.min.y, box.min.z); +// v2.set(box.max.x, box.max.y, box.min.z); +// v3.set(box.min.x, box.max.y, box.min.z); +// v4.set(box.min.x, box.min.y, box.min.z); +// break; +// } +// } + +// export default class Select2DAction extends Action { +// static actionName: string = 'select-2d'; +// renderView: Image2DRenderView; + +// private _mouseDown: boolean = false; +// private _mouseDownPos: THREE.Vector2 = new THREE.Vector2(); +// private raycaster: THREE.Raycaster = new THREE.Raycaster(); +// private rotate180: THREE.Matrix4 = new THREE.Matrix4(); + +// constructor(renderView: Image2DRenderView) { +// super(); +// this.renderView = renderView; +// this.rotate180.makeRotationAxis(new THREE.Vector3(0, 0, 1), Math.PI); + +// this.onMouseDown = this.onMouseDown.bind(this); +// this.onMouseUp = this.onMouseUp.bind(this); +// } +// init() { +// let dom = this.renderView.renderer.domElement; +// // this._mouseDown = false; +// // this._mouseDownPos = new THREE.Vector2(); + +// dom.addEventListener('mousedown', this.onMouseDown); +// dom.addEventListener('mouseup', this.onMouseUp); +// } + +// get2DCanvasPos(box: THREE.Box3, matrix: THREE.Matrix4, face: IFace): THREE.Vector3[] { +// let v1 = get(THREE.Vector3, 0); +// let v2 = get(THREE.Vector3, 1); +// let v3 = get(THREE.Vector3, 2); +// let v4 = get(THREE.Vector3, 3); + +// getRect(v1, v2, v3, v4, box, face); + +// let result = [v1, v2, v3, v4]; + +// result.forEach((v, index) => { +// v.applyMatrix4(matrix); +// this.renderView.worldToCanvas(v); +// // v.applyMatrix4(this.renderView.matrixExternal); +// // if (v.z < 0) { +// // v.applyMatrix4(this.rotate180); +// // } +// // v.applyMatrix4(this.renderView.matrixInternal); +// // v.x /= v.z; +// // v.y /= v.z; +// // points[index].set(v.x, v.y); +// }); +// return result; +// } + +// isPointInRect(pos: THREE.Vector2, rect: THREE.Vector3[]) { +// let A = rect[0]; +// let B = rect[1]; +// let C = rect[2]; +// let D = rect[3]; +// let a = (B.x - A.x) * (pos.y - A.y) - (B.y - A.y) * (pos.x - A.x); +// let b = (C.x - B.x) * (pos.y - B.y) - (C.y - B.y) * (pos.x - B.x); +// let c = (D.x - C.x) * (pos.y - C.y) - (D.y - C.y) * (pos.x - C.x); +// let d = (A.x - D.x) * (pos.y - D.y) - (A.y - D.y) * (pos.x - D.x); +// if ((a > 0 && b > 0 && c > 0 && d > 0) || (a < 0 && b < 0 && c < 0 && d < 0)) { +// return true; +// } +// return false; +// } + +// getBoxByPos(pos: THREE.Vector2): Box | null { +// let { annotate3D } = this.renderView.pointCloud; +// let matrix = new THREE.Matrix4(); + +// for (let i = 0; i < annotate3D.children.length; i++) { +// let obj = annotate3D.children[i] as Box; +// if (!obj.visible) continue; + +// if (!obj.geometry.boundingBox) obj.geometry.computeBoundingBox(); + +// let bbox = obj.geometry.boundingBox as THREE.Box3; +// // matrix.multiplyMatrices(this.renderView.matrix, obj.matrixWorld); + +// for (let j = 0; j < faces.length; j++) { +// let face = faces[j]; +// // let points = [get(THREE.Vector2, 0), get(THREE.Vector2, 1), get(THREE.Vector2, 2), get(THREE.Vector2, 3)]; +// let points = this.get2DCanvasPos(bbox, obj.matrixWorld, face); +// if (points[0].z < 0 && points[1].z < 0 && points[2].z < 0 && points[3].z < 0) +// continue; +// if (this.isPointInRect(pos, points)) { +// // console.log(face, obj); +// return obj; +// } +// } +// } + +// return null; +// } + +// onMouseDown(event: MouseEvent) { +// if (!this.enabled) return; + +// this._mouseDown = true; +// this._mouseDownPos.set(event.offsetX, event.offsetY); +// } +// onMouseUp(event: MouseEvent) { +// if (!this.enabled) return; + +// // let { annotate3D } = this.renderView.pointCloud; +// let tempVec2 = new THREE.Vector2(); +// let domElement = this.renderView.renderer.domElement; +// let distance = tempVec2.set(event.offsetX, event.offsetY).distanceTo(this._mouseDownPos); + +// let canvasPos = tempVec2.set(event.offsetX, event.offsetY); +// // let imagePos = tempVec2.set(event.offsetX / domElement.clientWidth, event.offsetY / domElement.clientHeight).multiply(this.renderView.imgSize); +// // console.log('distance:',distance) +// if (this._mouseDown && distance < 0.1) { +// let obj = this.getBoxByPos(canvasPos); +// // console.log(obj); +// if (obj) this.renderView.pointCloud.selectObject(obj); +// else { +// this.renderView.pointCloud.selectObject(); +// } +// } + +// this._mouseDown = false; +// } + +// getPrejectPos(event: MouseEvent) { +// let x = (event.offsetX / this.renderView.width) * 2 - 1; +// let y = (-event.offsetY / this.renderView.height) * 2 + 1; +// return { x, y }; +// } +// } diff --git a/frontend/text-tool/src/packages/pc-render/action/Transform2DAction.ts b/frontend/text-tool/src/packages/pc-render/action/Transform2DAction.ts new file mode 100644 index 00000000..ead48196 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/action/Transform2DAction.ts @@ -0,0 +1,91 @@ +import * as THREE from 'three'; +import Image2DRenderView from '../renderView/Image2DRenderView'; +import { Event } from '../config'; +import { isLeft, isRight } from '../utils'; +import Action from './Action'; +import * as _ from 'lodash'; + +let temp = new THREE.Matrix4(); +export default class Transform2DAction extends Action { + static actionName: string = 'transform-2d'; + renderView: Image2DRenderView; + container: HTMLDivElement; + constructor(renderView: Image2DRenderView) { + super(); + + this.renderView = renderView; + this.onZoom = this.onZoom.bind(this); + this.onMousedown = this.onMousedown.bind(this); + this.onRender = this.onRender.bind(this); + + this.container = this.renderView.container.parentNode as HTMLDivElement; + } + + init() { + this.container.addEventListener('wheel', this.onZoom); + this.container.addEventListener('mousedown', this.onMousedown); + + // this.renderView.addEventListener(Event.RENDER_AFTER, this.onRender); + } + + onRender() { + // console.log('onRender'); + let { container, containerMatrix } = this.renderView; + // let m = containerMatrix.elements; + // container.style.transform = `matrix(${m[0]},${m[1]},${m[4]},${m[5]},${m[12]},${m[13]})`; + this.renderView.render(); + this.renderView.dispatchEvent({ type: Event.CONTAINER_TRANSFORM }); + } + + onMousedown(e: MouseEvent) { + if (isLeft(e)) return; + + let { containerMatrix } = this.renderView; + let startPos = new THREE.Vector2(e.clientX, e.clientY); + let curPos = new THREE.Vector2(); + let startMatrix = containerMatrix.clone(); + let matrix = new THREE.Matrix4(); + + let onDocMove = _.throttle((e: MouseEvent) => { + curPos.set(e.clientX, e.clientY).sub(startPos); + matrix.makeTranslation(curPos.x, curPos.y, 0); + containerMatrix.multiplyMatrices(matrix, startMatrix); + this.onRender(); + }, 40); + function onDocUp() { + document.removeEventListener('mousemove', onDocMove); + document.removeEventListener('mouseup', onDocUp); + } + + document.addEventListener('mousemove', onDocMove); + document.addEventListener('mouseup', onDocUp); + } + + onZoom(e: WheelEvent) { + let containerScale = this.renderView.containerMatrix.elements[0] as number; + let { containerMatrix } = this.renderView; + const zoom = e.deltaY > 0 ? 0.95 : 1.05; + + let newScale = containerScale * zoom; + if (newScale < 0.1 || newScale > 10) return; + + let bbox = this.container.getBoundingClientRect(); + let offsetX = e.clientX - bbox.x; + let offsetY = e.clientY - bbox.y; + + let matrix = temp; + + containerMatrix.premultiply(matrix.makeTranslation(-offsetX, -offsetY, 0)); + containerMatrix.premultiply(matrix.makeScale(zoom, zoom, 1)); + containerMatrix.premultiply(matrix.makeTranslation(offsetX, offsetY, 0)); + + this.onRender(); + } + + reset() { + let { containerMatrix } = this.renderView; + containerMatrix.identity(); + this.onRender(); + } + destroy() {} +} diff --git a/frontend/text-tool/src/packages/pc-render/action/TransformControlsAction.ts b/frontend/text-tool/src/packages/pc-render/action/TransformControlsAction.ts new file mode 100644 index 00000000..4a04a752 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/action/TransformControlsAction.ts @@ -0,0 +1,74 @@ +import { TransformControls } from '../common/TransformControls'; +// import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js'; +import MainRenderView from '../renderView/MainRenderView'; +import Action from './Action'; +import { Event } from '../config'; +import Box from '../objects/Box'; +import * as THREE from 'three'; +import * as _ from 'lodash'; + +export default class TransformControlsAction extends Action { + static actionName: string = 'transform-control'; + renderView: MainRenderView; + // control: TransformControls; + control: any; + constructor(renderView: MainRenderView) { + super(); + + this.renderView = renderView; + + this.control = new TransformControls(renderView.camera, renderView.renderer.domElement); + this.control.space = 'local'; + this.controlChange = this.controlChange.bind(this); + this.controlObjectChange = this.controlObjectChange.bind(this); + this.draggingChange = this.draggingChange.bind(this); + this.selectChange = this.selectChange.bind(this); + } + init() { + this.control.addEventListener('change', this.controlChange); + this.control.addEventListener('objectChange', this.controlObjectChange); + this.control.addEventListener('dragging-changed', this.draggingChange); + this.renderView.pointCloud.scene.add(this.control); + this.renderView.pointCloud.addEventListener(Event.SELECT, this.selectChange); + } + + toggle(enabled: boolean) { + this.control.enabled = enabled; + } + + selectChange() { + // debugger + let { selection } = this.renderView.pointCloud; + + // if (selection.length === 1 && selection[0] instanceof THREE.Object3D) { + // this.control.attach(selection[0]); + // } else { + // this.control.detach(); + // } + if (selection.length === 0 || selection[0] !== this.control.object) { + this.control.detach(); + } + } + + draggingChange(event: any) { + this.renderView.actionMap['orbit-control'].toggle(!event.value); + } + controlChange(event: any) { + // console.log(event); + // this.updateTransform(event.data); + this.renderView.pointCloud.render(); + } + controlObjectChange(event: any) { + if (event.data && this.control.object) this.updatePosition(event.data); + this.renderView.pointCloud.render(); + } + + updatePosition(position: THREE.Vector3) { + this.control.object.position.copy(position); + this.renderView.pointCloud.dispatchEvent({ + type: Event.OBJECT_TRANSFORM, + data: { object: this.control.object }, + }); + // this.renderView.pointCloud.render(); + } +} diff --git a/frontend/text-tool/src/packages/pc-render/action/ViewHelperAction.ts b/frontend/text-tool/src/packages/pc-render/action/ViewHelperAction.ts new file mode 100644 index 00000000..4d4bb287 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/action/ViewHelperAction.ts @@ -0,0 +1,48 @@ +import MainRenderView from '../renderView/MainRenderView'; +import Action from './Action'; +import * as THREE from 'three'; +import { ViewHelper, UIElement, viewType } from '../common/ViewHelper'; +import OrbitControlsAction from './OrbitControlsAction'; +import { Event } from '../config'; + +export default class ViewHelperAction extends Action { + static actionName: string = 'view-helper'; + private listener: () => void; + renderView: MainRenderView; + viewHelper: ViewHelper; + constructor(renderView: MainRenderView) { + super(); + this.enabled = true; + this.renderView = renderView; + const controls = (renderView.actionMap['orbit-control'] as OrbitControlsAction).control; + let viewHelper = new ViewHelper(renderView, controls); + + const dom = document.createElement('div'); + dom.style.cssText = 'position:absolute;right:0px;bottom:0px;height:128px;width:128px;'; + const panel = new UIElement(dom); + panel.setId('viewHelper'); + panel.dom.addEventListener('pointerup', (event: MouseEvent) => { + event.stopPropagation(); + viewHelper.handleClick(event); + }); + + panel.dom.addEventListener('pointerdown', function (event: MouseEvent) { + event.stopPropagation(); + }); + renderView.container.appendChild(panel.dom); + this.listener = () => { + viewHelper.render(renderView.renderer); + }; + this.viewHelper = viewHelper; + } + view(type: viewType): Promise { + return this.viewHelper.view(type); + } + destroy(): void { + this.renderView.removeEventListener(Event.RENDER_AFTER, this.listener); + } + init() { + let renderView = this.renderView; + renderView.addEventListener(Event.RENDER_AFTER, this.listener); + } +} diff --git a/frontend/text-tool/src/packages/pc-render/action/index.ts b/frontend/text-tool/src/packages/pc-render/action/index.ts new file mode 100644 index 00000000..3fbea546 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/action/index.ts @@ -0,0 +1,13 @@ +import Action from './Action'; + +export type ActionCtr = new (args: any) => Action; + +type IActionMap = { [key: string]: ActionCtr }; + +const Actions: IActionMap = {}; + +export function registryAction(name: string, action: new () => T) { + Actions[name] = action; +} + +export default Actions; diff --git a/frontend/text-tool/src/packages/pc-render/common/BasicSvg.ts b/frontend/text-tool/src/packages/pc-render/common/BasicSvg.ts new file mode 100644 index 00000000..104bd2e1 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/common/BasicSvg.ts @@ -0,0 +1,892 @@ +import { Object2D, Rect, Box2D, Vector2Of4 } from '../objects'; +import * as THREE from 'three'; +import { get } from '../utils/tempVar'; +import * as _ from 'lodash'; +// type DragHandler = (offsetLocal: THREE.Vector3, offsetCamera: THREE.Vector2) => ITransform | null; +const tempV2_1 = new THREE.Vector2(); +export type ClearHandler = () => void; +// svg element attribute 'data-info' +export type Info = + | 'lt' + | 'lb' + | 'rt' + | 'rb' + | 'wrap-4' + | 'wrap-8' + | 'wrap-front' + | 'wrap-back' + | 'wrap-bg' + | 'top' + | 'bottom' + | 'left' + | 'right' + | 'front-bg' + | 'back-bg' + | 'bg' + | 'line' + | 'rotate'; +// event type +export type IEvent = 'start' | 'update' | 'end' | 'start-rotate' | 'rotate' | 'end-rotate'; +export type IDir = 'front' | 'back' | 'bg'; +export interface IBaseEvent extends THREE.Event { + type: IEvent; + event: MouseEvent; + info?: Info; +} + +export interface IRectEvent extends IBaseEvent { + data?: { + center?: THREE.Vector2; + size?: THREE.Vector2; + detailAngle?: number; + offsetAngle?: number; // -PI ~ PI, inverse + , clockwise -; + }; +} +export interface IBoxEvent extends IBaseEvent { + dir: IDir; + data?: { + positions1: Vector2Of4; + positions2: Vector2Of4; + }; +} + +// create svg dom + +export function createSvg(): SVGElement { + var ns = 'http://www.w3.org/2000/svg'; + var element = document.createElementNS(ns, 'svg') as SVGElement; + return element; +} +// +export function createRectTag( + tag: string, + info: Info, + attrsMap: Record = {}, +): Element { + var ns = 'http://www.w3.org/2000/svg'; + var element = document.createElementNS(ns, tag); + Object.keys(attrsMap).forEach((attr) => { + element.setAttributeNS(null, attr, attrsMap[attr]); + }); + element.setAttributeNS(null, 'data-info', info); + if (tag === 'circle' && info !== 'rotate') { + element.classList.add(`rect-dot`); + } else if (tag === 'rect' && info !== 'bg') { + element.classList.add(`rect-line`); + } else if (tag === 'line') { + element.classList.add('rect-line', 'line-border'); + } + + element.classList.add(`rect-${info}`); + return element; +} +export interface IRectOption { + minSize: THREE.Vector2; + moveAble: boolean; + rotateAble: boolean; + borderAble: boolean; + circleAble: boolean; + lineStyle?: { + 'stroke-dasharray'?: number; + stroke?: string; + 'stroke-width'?: number; + opacity?: number; + }; + circleStyle?: { + fill?: string; + 'stroke-width'?: number; + stroke?: string; + opacity?: number; + }; + rotateStyle?: { + fill?: string; + 'stroke-width'?: number; + stroke?: string; + opacity?: number; + }; +} +export interface IBoxOption { + moveAble: boolean; + circleAble: boolean; + borderAble: boolean; + lineStyle?: { + stroke?: string; + 'stroke-width'?: number; + opacity?: number; + }; + circleStyle?: { + fill?: string; + 'stroke-width'?: number; + stroke?: string; + opacity?: number; + }; +} +function getDotDir(info: Info, dir: THREE.Vector2) { + switch (info) { + case 'lt': + dir.set(-1, -1); + break; + case 'lb': + dir.set(-1, 1); + break; + case 'rt': + dir.set(1, -1); + break; + case 'rb': + dir.set(1, 1); + break; + case 'top': + dir.set(0, -1); + break; + case 'bottom': + dir.set(0, 1); + break; + case 'left': + dir.set(-1, 0); + break; + case 'right': + dir.set(1, 0); + break; + } +} + +function getBox2DIndex(info: Info) { + let index = 0; + switch (info) { + case 'lt': + case 'left': + index = 0; + break; + case 'lb': + case 'bottom': + index = 1; + break; + case 'rb': + case 'right': + index = 2; + break; + case 'rt': + case 'top': + index = 3; + break; + } + return index; +} + +// set svg element css of key-value +function setStyle( + nodeList: NodeListOf | SVGElement, + styleObject: Record, +) { + let cssText = ''; + for (let key in styleObject) { + cssText += `${key}:${styleObject[key]};`; + } + if (cssText) { + (nodeList instanceof NodeList ? nodeList : [nodeList]).forEach((item: SVGElement) => { + item.style.cssText = cssText; + }); + } +} +export class BasicSvg extends THREE.EventDispatcher { + svg: SVGElement; + padding = new THREE.Vector2(1000, 1000); + container: HTMLDivElement; + clearCall: ClearHandler[] = []; + constructor(container: HTMLDivElement) { + super(); + const { x, y } = this.padding; + const svg = createSvg(); + svg.style.position = 'absolute'; + svg.style.width = `calc(100% + ${x * 2}px)`; + svg.style.height = `calc(100% + ${y * 2}px)`; + svg.style.marginTop = `-${x}px`; + svg.style.marginLeft = `-${y}px`; + svg.style.pointerEvents = 'none'; + svg.style.width = `calc(100% + ${x * 2}px)`; + svg.style.left = '0'; + svg.style.top = '0'; + svg.classList.add('edit-2d-wrap'); + + container.appendChild(svg); + + this.container = container; + this.svg = svg; + } + getZoom() { + const matrix = this.container.style.transform.match(/(?<=matrix\()[0-9.]+/); + const zoom = matrix ? +matrix[0] : 1; + return zoom; + } + isLeft(e: MouseEvent) { + return e.button === 0; + } + isRight(e: MouseEvent) { + return e.button === 2; + } + destroy() { + this.clearCall.forEach((fn) => fn()); + this.container.removeChild(this.svg); + } + show() { + this.svg.style.display = 'block'; + } + hide() { + this.svg.style.display = 'none'; + } +} + +export class RectTool extends BasicSvg { + static toolName: string = '2d-rect'; + rectWrap = {} as SVGGElement; + rotateWrap = {} as SVGGElement; + rotateCircle = {} as SVGCircleElement; + lineSize: number = 4; + rotation = 0; + option: IRectOption = { + minSize: new THREE.Vector2(0, 0), + moveAble: true, + rotateAble: true, + borderAble: true, + circleAble: true, + }; + center = new THREE.Vector2(); + size = new THREE.Vector2(1, 1); + constructor(container: HTMLDivElement) { + super(container); + this.rectWrap = createRectTag('g', 'wrap-4') as SVGGElement; + this.init(); + } + init() { + let rectWrap = this.rectWrap; + this.createRectInfo(rectWrap); + this.svg.appendChild(rectWrap); + this.initDrag(this.rectWrap); + this.initRotate(); + } + setOption(option: Partial) { + Object.assign(this.option, option); + let { circleStyle, lineStyle, rotateStyle } = option; + if (circleStyle) { + setStyle(this.rectWrap.querySelectorAll('circle'), circleStyle); + } + if (lineStyle) { + const node = this.rectWrap.querySelector('rect.rect-bg') as SVGRectElement; + if (node) setStyle(node, lineStyle); + } + if (rotateStyle) { + setStyle(this.rotateCircle, rotateStyle); + } + this.checkRotate(); + } + initRotate() { + let rotateWrap = createRectTag('g', 'wrap-4') as SVGGElement; + let rotateCircle = createRectTag('circle', 'rotate', { + cx: '-2', + cy: '-2', + r: '6', + }) as SVGCircleElement; + rotateWrap.appendChild(rotateCircle); + this.svg.appendChild(rotateWrap); + this.initRotateEvent(rotateCircle); + this.rotateWrap = rotateWrap; + this.rotateCircle = rotateCircle; + } + createRectInfo( + rectWrap: SVGGElement, + config: { border?: boolean; dot?: boolean; bg?: boolean; rotate?: boolean } = { + border: true, + dot: true, + bg: true, + rotate: true, + }, + ) { + if (config.bg) { + let bg = createRectTag('rect', 'bg', { + x: '-2', + y: '-2', + width: '4', + height: '4', + }) as SVGCircleElement; + rectWrap.appendChild(bg); + } + + if (config.border) { + let lineTop = createRectTag('rect', 'top', { + x: '-2', + y: '-2', + width: '4', + height: '4', + }) as SVGCircleElement; + let lineBottom = createRectTag('rect', 'bottom', { + x: '-2', + y: '-2', + width: '4', + height: '4', + }) as SVGCircleElement; + let lineLeft = createRectTag('rect', 'left', { + x: '-2', + y: '-2', + width: '4', + height: '4', + }) as SVGCircleElement; + let lineRight = createRectTag('rect', 'right', { + x: '-2', + y: '-2', + width: '4', + height: '4', + }) as SVGCircleElement; + + rectWrap.appendChild(lineTop); + rectWrap.appendChild(lineBottom); + rectWrap.appendChild(lineLeft); + rectWrap.appendChild(lineRight); + } + + if (config.dot) { + let dotLT = createRectTag('circle', 'lt', { + cx: '0', + cy: '0', + r: '4', + }) as SVGCircleElement; + + let dotLB = createRectTag('circle', 'lb', { + cx: '0', + cy: '0', + r: '4', + }) as SVGCircleElement; + + let dotRT = createRectTag('circle', 'rt', { + cx: '0', + cy: '0', + r: '4', + }) as SVGCircleElement; + + let dotRB = createRectTag('circle', 'rb', { + cx: '-2', + cy: '-2', + r: '4', + }) as SVGCircleElement; + rectWrap.appendChild(dotLT); + rectWrap.appendChild(dotLB); + rectWrap.appendChild(dotRB); + rectWrap.appendChild(dotRT); + } + } + updateRect(size: THREE.Vector2, center: THREE.Vector2) { + if (isNaN(size.x) || isNaN(size.y) || isNaN(center.x) || isNaN(center.y)) return; + this.size.copy(size); + this.center.copy(center); + let childNodes = this.rectWrap.childNodes; + let dir = get(THREE.Vector2, 0); + let newPos = get(THREE.Vector2, 1); + childNodes.forEach((node) => { + if (node.nodeName === 'circle') { + let circle = node as SVGCircleElement; + let info = circle.getAttributeNS(null, 'data-info') as Info; + getDotDir(info, dir); + dir.multiplyScalar(0.5); + newPos.copy(size).multiply(dir).add(center); + newPos.add(this.padding); + + // circle.style.transform = `translate(${newPos.x}px, ${newPos.y}px)`; + circle.setAttributeNS(null, 'cx', newPos.x.toFixed(2)); + circle.setAttributeNS(null, 'cy', newPos.y.toFixed(2)); + } else { + let rect = node as SVGRectElement; + let info = rect.getAttributeNS(null, 'data-info') as Info; + let width = 0; + let height = 0; + + if (info === 'bg') { + dir.set(-0.5, -0.5); + newPos.copy(size).multiply(dir).add(center); + newPos.add(this.padding); + + width = size.x; + height = size.y; + } else { + getDotDir(info, dir); + dir.multiplyScalar(0.5); + newPos.copy(size).multiply(dir).add(center); + newPos.add(this.padding); + + if (info === 'top' || info === 'bottom') { + width = size.x; + height = this.lineSize; + } else { + width = this.lineSize; + height = size.y; + } + newPos.x -= width / 2; + newPos.y -= height / 2; + } + + rect.setAttributeNS(null, 'x', newPos.x.toFixed(2)); + rect.setAttributeNS(null, 'y', newPos.y.toFixed(2)); + rect.setAttributeNS(null, 'width', width.toFixed(2)); + rect.setAttributeNS(null, 'height', height.toFixed(2)); + } + }); + + const rotatePos = tempV2_1.copy(center).add(this.padding); + rotatePos.y -= size.y / 2 - 10; + this.rotateCircle.setAttributeNS(null, 'cx', rotatePos.x.toFixed(2)); + this.rotateCircle.setAttributeNS(null, 'cy', rotatePos.y.toFixed(2)); + } + checkRotate() { + if (this.option.rotateAble) { + this.rotateWrap.style.display = 'block'; + return true; + } else { + this.rotateWrap.style.display = 'none'; + return false; + } + } + updateRotation() { + if (!this.checkRotate()) return; + + const center = this.center; + const { x, y } = this.padding; + const rotation = this.rotation; + this.svg.style.transform = `rotate(${rotation}deg)`; + this.svg.style.transformOrigin = `${center.x + x}px ${center.y + y}px`; + } + + initDrag(dom: SVGGElement) { + let scope = this; + + let currentPos = new THREE.Vector2(); + let startPos = new THREE.Vector2(); + const dir = new THREE.Vector2(); + let offsetPos = new THREE.Vector2(); + let startClient = new THREE.Vector2(); + let lastPos = new THREE.Vector2(); + const posInfo = new THREE.Vector2(); + const center = new THREE.Vector2(); + const size = new THREE.Vector2(); + dom.addEventListener('mousedown', onmousedown); + + function onmousedown(e: MouseEvent) { + if (scope.isRight(e)) return; + + let target = e.target as SVGElement; + let minSize = scope.option.minSize; + let bbox = scope.svg.getBoundingClientRect(); + startClient.set(bbox.x, bbox.y); + + e.preventDefault(); + e.stopPropagation(); + + let info = target.getAttributeNS(null, 'data-info') as Info; + const { rotateAble, moveAble, borderAble, circleAble } = scope.option; + if (!rotateAble && info === 'rotate') return; + else if (!moveAble && info === 'bg') return; + else if (!borderAble && ['left', 'right', 'top', 'bottom'].indexOf(info) >= 0) return; + if (info === 'bg') { + posInfo.copy(scope.center); + } else { + getDotDir(info, dir); + posInfo.copy(scope.center).add(dir.multiply(scope.size).multiplyScalar(-0.5)); + } + + startPos.set(e.clientX, e.clientY).sub(startClient); + lastPos.copy(startPos); + const zoom = scope.getZoom(); + scope.dispatchEvent({ + type: 'start', + target, + event: e, + info, + }); + let onDocMove = _.throttle((e: MouseEvent) => { + currentPos.set(e.clientX, e.clientY).sub(startClient); + offsetPos.copy(currentPos).sub(startPos).divideScalar(zoom); + currentPos.divideScalar(zoom).sub(scope.padding); + if (info === 'top' || info === 'bottom') { + size.set(scope.size.x, Math.abs(currentPos.y - posInfo.y)); + center.set(scope.center.x, (currentPos.y + posInfo.y) / 2); + } else if (info === 'left' || info === 'right') { + center.set((currentPos.x + posInfo.x) / 2, scope.center.y); + size.set(Math.abs(currentPos.x - posInfo.x), scope.size.y); + } else if (info === 'bg') { + center.set(offsetPos.x + posInfo.x, posInfo.y + offsetPos.y); + size.copy(scope.size); + } else { + center.set((currentPos.x + posInfo.x) / 2, (currentPos.y + posInfo.y) / 2); + size.set( + Math.abs(currentPos.x - posInfo.x), + Math.abs(currentPos.y - posInfo.y), + ); + } + + if (size.x < minSize.x) { + center.x = scope.center.x; + size.x = scope.size.x; + } + if (size.y < minSize.y) { + center.y = scope.center.y; + size.y = scope.size.y; + } + + scope.dispatchEvent({ + type: 'update', + target, + event: e, + info, + data: { + center, + size, + }, + }); + }, 30); + function onDocUp(e: MouseEvent) { + scope.dispatchEvent({ type: 'end', event: e, info: info }); + document.removeEventListener('mousemove', onDocMove); + document.removeEventListener('mouseup', onDocUp); + } + + document.addEventListener('mousemove', onDocMove); + document.addEventListener('mouseup', onDocUp); + } + } + initRotateEvent(circle: SVGCircleElement) { + const scope = this; + let startClient = new THREE.Vector2(); + let currentPos = new THREE.Vector2(); + let startPos = new THREE.Vector2(); + let sAngle = 0; + let lastAngle = 0; + let eAngle = 0; + let lastPos = new THREE.Vector2(); + + circle.addEventListener('mousedown', onmousedown); + + function onmousedown(e: MouseEvent) { + if (scope.isRight(e)) return; + e.preventDefault(); + e.stopPropagation(); + if (!scope.option.rotateAble) return; + let bbox = scope.svg.getBoundingClientRect(); + const zoom = scope.getZoom(); + const center = scope.center; + startClient.set(bbox.x, bbox.y); + startPos + .set(e.clientX, e.clientY) + .sub(startClient) + .divideScalar(zoom) + .sub(scope.padding); + lastPos.copy(startPos); + startPos.sub(center); + sAngle = Math.atan2(startPos.y, startPos.x); + lastAngle = sAngle; + scope.dispatchEvent({ + type: 'start-rotate', + event: e, + }); + + let onDocMove = (e: MouseEvent) => { + currentPos + .set(e.clientX, e.clientY) + .sub(startClient) + .divideScalar(zoom) + .sub(scope.padding); + currentPos.sub(center); + eAngle = Math.atan2(currentPos.y, currentPos.x); + let angle = sAngle - eAngle; + if (angle < 0) { + angle += Math.PI * 2; + } + if (angle > Math.PI) { + angle -= Math.PI * 2; + } + scope.dispatchEvent({ + type: 'rotate', + event: e, + data: { + detailAngle: eAngle - lastAngle, + offsetAngle: angle, + }, + }); + scope.rotation = -(angle * 180) / Math.PI; + lastAngle = eAngle; + }; + + function onDocUp(e: MouseEvent) { + scope.dispatchEvent({ + type: 'end-rotate', + event: e, + }); + document.removeEventListener('mousemove', onDocMove); + document.removeEventListener('mouseup', onDocUp); + } + + document.addEventListener('mousemove', onDocMove); + document.addEventListener('mouseup', onDocUp); + } + } +} +export class Box3DTool extends BasicSvg { + static toolName = '3d-cube'; + box2DWrap: SVGGElement = {} as SVGGElement; + positions1: Vector2Of4; + positions2: Vector2Of4; + option: IBoxOption = { + moveAble: true, + circleAble: true, + borderAble: true, + }; + constructor(container: HTMLDivElement) { + super(container); + this.box2DWrap = createRectTag('g', 'wrap-8') as SVGGElement; + this.positions1 = this.getEmptyVector2Of4(); + this.positions2 = this.getEmptyVector2Of4(); + this.init(); + } + getEmptyVector2Of4(): Vector2Of4 { + return [new THREE.Vector2(), new THREE.Vector2(), new THREE.Vector2(), new THREE.Vector2()]; + } + copyVector2Of4(target: Vector2Of4, from: Vector2Of4) { + target.forEach((pos, index) => { + pos.copy(from[index]); + }); + } + setOption(option: Partial) { + Object.assign(this.option, option); + let { circleStyle, lineStyle } = this.option; + if (circleStyle) { + setStyle(this.svg.querySelectorAll('circle'), circleStyle); + } + if (lineStyle) { + setStyle(this.svg.querySelectorAll('line'), lineStyle); + } + } + init() { + let boxWrap = this.box2DWrap; + boxWrap.classList.add('wrap-8'); + + let bgWrap = createRectTag('g', 'wrap-bg') as SVGGElement; + let bg1 = createRectTag('polygon', 'front-bg') as SVGPolygonElement; + let bg2 = createRectTag('polygon', 'back-bg') as SVGPolygonElement; + bgWrap.append(bg1); + bgWrap.append(bg2); + + let font = createRectTag('g', 'wrap-front') as SVGGElement; + this.createRectInfo(font, { dot: true, border: true }); + + let back = createRectTag('g', 'wrap-back') as SVGGElement; + this.createRectInfo(back, { dot: true, border: true }); + + boxWrap.appendChild(bgWrap); + boxWrap.appendChild(font); + boxWrap.appendChild(back); + + this.box2DWrap = boxWrap; + this.svg.appendChild(this.box2DWrap); + + this.initBoxDrag(bgWrap, 'bg'); + this.initBoxDrag(font, 'front'); + this.initBoxDrag(back, 'back'); + } + createRectInfo( + rectWrap: SVGGElement, + config: { border?: boolean; dot?: boolean; bg?: boolean } = { + border: true, + dot: true, + bg: true, + }, + ) { + if (config.bg) { + let bg = createRectTag('rect', 'bg', { + x: '-2', + y: '-2', + width: '4', + height: '4', + }) as SVGCircleElement; + rectWrap.appendChild(bg); + } + if (config.border) { + const infos: Info[] = ['left', 'right', 'top', 'bottom']; + infos.forEach((info) => { + const tag = createRectTag('line', info, { + x1: '-2', + x2: '-2', + y1: '-2', + y2: '-2', + stroke: 'red', + 'stroke-width': '4', + }); + rectWrap.appendChild(tag); + }); + } + + if (config.dot) { + let dotLT = createRectTag('circle', 'lt', { + cx: '0', + cy: '0', + r: '4', + }) as SVGCircleElement; + + let dotLB = createRectTag('circle', 'lb', { + cx: '0', + cy: '0', + r: '4', + }) as SVGCircleElement; + + let dotRT = createRectTag('circle', 'rt', { + cx: '0', + cy: '0', + r: '4', + }) as SVGCircleElement; + + let dotRB = createRectTag('circle', 'rb', { + cx: '-2', + cy: '-2', + r: '4', + }) as SVGCircleElement; + rectWrap.appendChild(dotLT); + rectWrap.appendChild(dotLB); + rectWrap.appendChild(dotRB); + rectWrap.appendChild(dotRT); + } + } + initBoxDrag(dom: SVGGElement, dir: IDir) { + let scope = this; + let currentPos = new THREE.Vector2(); + let offsetPos = new THREE.Vector2(); + let startPos = new THREE.Vector2(); + let lastPos = new THREE.Vector2(); + let startClient = new THREE.Vector2(); + const positions1 = this.getEmptyVector2Of4(); + const positions2 = this.getEmptyVector2Of4(); + dom.addEventListener('mousedown', onmousedown); + + function onmousedown(e: MouseEvent) { + if (scope.isRight(e)) return; + e.preventDefault(); + e.stopPropagation(); + let target = e.target as SVGCircleElement | SVGLineElement; + + let bbox = scope.svg.getBoundingClientRect(); + startClient.set(bbox.x, bbox.y); + + const zoom = scope.getZoom(); + let info = target.getAttributeNS(null, 'data-info') as Info; + + let { moveAble, circleAble, borderAble } = scope.option; + if (!borderAble && target.nodeName === 'line') return; + else if (!circleAble && target.nodeName === 'circle') return; + else if (!moveAble && info === 'bg') return; + + startPos.set(e.clientX, e.clientY).sub(startClient); + lastPos.copy(startPos); + const eventStart: IBoxEvent = { + dir, + type: 'start', + target, + event: e, + info, + }; + const posIndex = getBox2DIndex(info); + scope.dispatchEvent(eventStart); + let onDocMove = _.throttle((e: MouseEvent) => { + scope.copyVector2Of4(positions1, scope.positions1); + scope.copyVector2Of4(positions2, scope.positions2); + currentPos.set(e.clientX, e.clientY).sub(startClient); + offsetPos.copy(currentPos).sub(lastPos).divideScalar(zoom); + if (info === 'front-bg') { + positions1.forEach((pos) => pos.add(offsetPos)); + } else if (info === 'back-bg') { + positions2.forEach((pos) => pos.add(offsetPos)); + } else if (dir === 'front') { + positions1[posIndex].add(offsetPos); + if (target.nodeName === 'line') { + let posIndex1 = (posIndex + 1) % 4; + positions1[posIndex1].add(offsetPos); + } + } else if (dir === 'back') { + positions2[posIndex].add(offsetPos); + if (target.nodeName === 'line') { + let posIndex1 = (posIndex + 1) % 4; + positions1[posIndex1].add(offsetPos); + } + } + const event: IBoxEvent = { + type: 'update', + target, + event: e, + dir, + info, + data: { + positions1, + positions2, + }, + }; + lastPos.copy(currentPos); + scope.dispatchEvent(event); + }, 30); + function onDocUp(e: MouseEvent) { + scope.dispatchEvent({ type: 'end', event: e, dir, info: info }); + document.removeEventListener('mousemove', onDocMove); + document.removeEventListener('mouseup', onDocUp); + } + + document.addEventListener('mousemove', onDocMove); + document.addEventListener('mouseup', onDocUp); + } + } + updateBox2D(positions1: Vector2Of4, positions2: Vector2Of4) { + let childNodes = this.box2DWrap.childNodes; + this.copyVector2Of4(this.positions1, positions1); + this.copyVector2Of4(this.positions2, positions2); + + this.updateBox2DRect(childNodes.item(0)); + this.updateBox2DRect(childNodes.item(1)); + this.updateBox2DRect(childNodes.item(2)); + } + updateBox2DRect(wrap: ChildNode) { + let newPos = get(THREE.Vector2, 1); + + let childNodes = wrap.childNodes; + let positions; + let wrapInfo = (wrap as SVGElement).getAttributeNS(null, 'data-info') as Info; + childNodes.forEach((e) => { + let node = e as SVGElement; + let info = node.getAttributeNS(null, 'data-info') as Info; + + if (node.nodeName === 'circle') { + positions = wrapInfo === 'wrap-front' ? this.positions1 : this.positions2; + let index = getBox2DIndex(info); + let pos = positions[index]; + newPos.copy(pos).add(this.padding); + + node.setAttributeNS(null, 'cx', newPos.x.toFixed(2)); + node.setAttributeNS(null, 'cy', newPos.y.toFixed(2)); + } else if (node.nodeName === 'line') { + positions = wrapInfo === 'wrap-front' ? this.positions1 : this.positions2; + let sIndex = getBox2DIndex(info); + let eIndex = (sIndex + 1) % 4; + const sPos = positions[sIndex]; + const ePos = positions[eIndex]; + newPos.copy(sPos).add(this.padding); + node.setAttributeNS(null, 'x1', newPos.x.toFixed(2)); + node.setAttributeNS(null, 'y1', newPos.y.toFixed(2)); + newPos.copy(ePos).add(this.padding); + node.setAttributeNS(null, 'x2', newPos.x.toFixed(2)); + node.setAttributeNS(null, 'y2', newPos.y.toFixed(2)); + } else { + positions = info === 'front-bg' ? this.positions1 : this.positions2; + let points = this.getPointsStr(positions); + node.setAttributeNS(null, 'points', points); + } + }); + } + getPointsStr(positions: THREE.Vector2[]) { + let str = ''; + let { x, y } = this.padding; + positions.forEach((pos) => { + str += `${(pos.x + x).toFixed(2)},${(pos.y + y).toFixed(2)} `; + }); + return str; + } +} diff --git a/frontend/text-tool/src/packages/pc-render/common/TransformControls.ts b/frontend/text-tool/src/packages/pc-render/common/TransformControls.ts new file mode 100644 index 00000000..c2bc7e6c --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/common/TransformControls.ts @@ -0,0 +1,1526 @@ +// @ts-nocheck +import { + BoxGeometry, + BufferGeometry, + CylinderGeometry, + DoubleSide, + Euler, + Float32BufferAttribute, + Line, + LineBasicMaterial, + Matrix4, + Mesh, + MeshBasicMaterial, + Object3D, + OctahedronGeometry, + PlaneGeometry, + Quaternion, + Raycaster, + SphereGeometry, + TorusGeometry, + Vector3, +} from 'three'; + +const _raycaster = new Raycaster(); + +const _tempVector = new Vector3(); +const _tempVector2 = new Vector3(); +const _tempQuaternion = new Quaternion(); +const _unit = { + X: new Vector3(1, 0, 0), + Y: new Vector3(0, 1, 0), + Z: new Vector3(0, 0, 1), +}; + +const _changeEvent = { type: 'change' }; +const _mouseDownEvent = { type: 'mouseDown' }; +const _mouseUpEvent = { type: 'mouseUp', mode: null }; +const _objectChangeEvent = { type: 'objectChange' }; + +class TransformControls extends Object3D { + constructor(camera, domElement) { + super(); + + if (domElement === undefined) { + console.warn( + 'THREE.TransformControls: The second parameter "domElement" is now mandatory.', + ); + domElement = document; + } + + this.visible = false; + this.domElement = domElement; + this.domElement.style.touchAction = 'none'; // disable touch scroll + + const _gizmo = new TransformControlsGizmo(); + this._gizmo = _gizmo; + this.add(_gizmo); + + const _plane = new TransformControlsPlane(); + this._plane = _plane; + this.add(_plane); + + const scope = this; + + // Defined getter, setter and store for a property + function defineProperty(propName, defaultValue) { + let propValue = defaultValue; + + Object.defineProperty(scope, propName, { + get: function () { + return propValue !== undefined ? propValue : defaultValue; + }, + + set: function (value) { + if (propValue !== value) { + propValue = value; + _plane[propName] = value; + _gizmo[propName] = value; + + scope.dispatchEvent({ type: propName + '-changed', value: value }); + scope.dispatchEvent(_changeEvent); + } + }, + }); + + scope[propName] = defaultValue; + _plane[propName] = defaultValue; + _gizmo[propName] = defaultValue; + } + + // Define properties with getters/setter + // Setting the defined property will automatically trigger change event + // Defined properties are passed down to gizmo and plane + + defineProperty('camera', camera); + defineProperty('object', undefined); + defineProperty('enabled', true); + defineProperty('axis', null); + defineProperty('mode', 'translate'); + defineProperty('translationSnap', null); + defineProperty('rotationSnap', null); + defineProperty('scaleSnap', null); + defineProperty('space', 'world'); + defineProperty('size', 1); + defineProperty('dragging', false); + defineProperty('showX', true); + defineProperty('showY', true); + defineProperty('showZ', true); + + // Reusable utility variables + + const worldPosition = new Vector3(); + const worldPositionStart = new Vector3(); + const worldQuaternion = new Quaternion(); + const worldQuaternionStart = new Quaternion(); + const cameraPosition = new Vector3(); + const cameraQuaternion = new Quaternion(); + const pointStart = new Vector3(); + const pointEnd = new Vector3(); + const rotationAxis = new Vector3(); + const rotationAngle = 0; + const eye = new Vector3(); + + // TODO: remove properties unused in plane and gizmo + + defineProperty('worldPosition', worldPosition); + defineProperty('worldPositionStart', worldPositionStart); + defineProperty('worldQuaternion', worldQuaternion); + defineProperty('worldQuaternionStart', worldQuaternionStart); + defineProperty('cameraPosition', cameraPosition); + defineProperty('cameraQuaternion', cameraQuaternion); + defineProperty('pointStart', pointStart); + defineProperty('pointEnd', pointEnd); + defineProperty('rotationAxis', rotationAxis); + defineProperty('rotationAngle', rotationAngle); + defineProperty('eye', eye); + + this._offset = new Vector3(); + this._startNorm = new Vector3(); + this._endNorm = new Vector3(); + this._cameraScale = new Vector3(); + + this._parentPosition = new Vector3(); + this._parentQuaternion = new Quaternion(); + this._parentQuaternionInv = new Quaternion(); + this._parentScale = new Vector3(); + + this._worldScaleStart = new Vector3(); + this._worldQuaternionInv = new Quaternion(); + this._worldScale = new Vector3(); + + this._positionStart = new Vector3(); + this._positionLast = new Vector3(); + this._quaternionStart = new Quaternion(); + this._scaleStart = new Vector3(); + + this._getPointer = getPointer.bind(this); + this._onPointerDown = onPointerDown.bind(this); + this._onPointerHover = onPointerHover.bind(this); + this._onPointerMove = onPointerMove.bind(this); + this._onPointerUp = onPointerUp.bind(this); + + this.domElement.addEventListener('pointerdown', this._onPointerDown); + this.domElement.addEventListener('pointermove', this._onPointerHover); + this.domElement.addEventListener('pointerup', this._onPointerUp); + } + + // updateMatrixWorld updates key transformation variables + updateMatrixWorld() { + if (this.object !== undefined) { + this.object.updateMatrixWorld(); + + if (this.object.parent === null) { + console.error( + 'TransformControls: The attached 3D object must be a part of the scene graph.', + ); + } else { + this.object.parent.matrixWorld.decompose( + this._parentPosition, + this._parentQuaternion, + this._parentScale, + ); + } + + this.object.matrixWorld.decompose( + this.worldPosition, + this.worldQuaternion, + this._worldScale, + ); + + this._parentQuaternionInv.copy(this._parentQuaternion).invert(); + this._worldQuaternionInv.copy(this.worldQuaternion).invert(); + } + + this.camera.updateMatrixWorld(); + this.camera.matrixWorld.decompose( + this.cameraPosition, + this.cameraQuaternion, + this._cameraScale, + ); + + this.eye.copy(this.cameraPosition).sub(this.worldPosition).normalize(); + + super.updateMatrixWorld(this); + } + + pointerHover(pointer) { + if (this.object === undefined || this.dragging === true) return; + + _raycaster.setFromCamera(pointer, this.camera); + + const intersect = intersectObjectWithRay(this._gizmo.picker[this.mode], _raycaster); + + if (intersect) { + this.axis = intersect.object.name; + } else { + this.axis = null; + } + } + + pointerDown(pointer) { + if (this.object === undefined || this.dragging === true || pointer.button !== 0) return; + + if (this.axis !== null) { + _raycaster.setFromCamera(pointer, this.camera); + + const planeIntersect = intersectObjectWithRay(this._plane, _raycaster, true); + + if (planeIntersect) { + this.object.updateMatrixWorld(); + this.object.parent.updateMatrixWorld(); + + this._positionStart.copy(this.object.position); + this._quaternionStart.copy(this.object.quaternion); + this._scaleStart.copy(this.object.scale); + + this.object.matrixWorld.decompose( + this.worldPositionStart, + this.worldQuaternionStart, + this._worldScaleStart, + ); + + this.pointStart.copy(planeIntersect.point).sub(this.worldPositionStart); + } + + this.dragging = true; + _mouseDownEvent.mode = this.mode; + this.dispatchEvent(_mouseDownEvent); + } + } + + pointerMove(pointer) { + const axis = this.axis; + const mode = this.mode; + const object = this.object; + let space = this.space; + let newPosition = new Vector3(); + + if (mode === 'scale') { + space = 'local'; + } else if (axis === 'E' || axis === 'XYZE' || axis === 'XYZ') { + space = 'world'; + } + + if ( + object === undefined || + axis === null || + this.dragging === false || + pointer.button !== -1 + ) + return; + + _raycaster.setFromCamera(pointer, this.camera); + + const planeIntersect = intersectObjectWithRay(this._plane, _raycaster, true); + + if (!planeIntersect) return; + + this.pointEnd.copy(planeIntersect.point).sub(this.worldPositionStart); + + if (mode === 'translate') { + // Apply translate + + this._offset.copy(this.pointEnd).sub(this.pointStart); + + if (space === 'local' && axis !== 'XYZ') { + this._offset.applyQuaternion(this._worldQuaternionInv); + } + + if (axis.indexOf('X') === -1) this._offset.x = 0; + if (axis.indexOf('Y') === -1) this._offset.y = 0; + if (axis.indexOf('Z') === -1) this._offset.z = 0; + + if (space === 'local' && axis !== 'XYZ') { + this._offset.applyQuaternion(this._quaternionStart).divide(this._parentScale); + } else { + this._offset.applyQuaternion(this._parentQuaternionInv).divide(this._parentScale); + } + + // object.position.copy(this._offset).add(this._positionStart); + newPosition.copy(this._offset).add(this._positionStart); + + // Apply translation snap + + if (this.translationSnap) { + if (space === 'local') { + newPosition.applyQuaternion( + _tempQuaternion.copy(this._quaternionStart).invert(), + ); + + if (axis.search('X') !== -1) { + newPosition.x = + Math.round(newPosition.x / this.translationSnap) * this.translationSnap; + } + + if (axis.search('Y') !== -1) { + newPosition.y = + Math.round(newPosition.y / this.translationSnap) * this.translationSnap; + } + + if (axis.search('Z') !== -1) { + newPosition.z = + Math.round(newPosition.z / this.translationSnap) * this.translationSnap; + } + + newPosition.applyQuaternion(this._quaternionStart); + } + + if (space === 'world') { + if (object.parent) { + newPosition.add( + _tempVector.setFromMatrixPosition(object.parent.matrixWorld), + ); + } + + if (axis.search('X') !== -1) { + newPosition.x = + Math.round(newPosition.x / this.translationSnap) * this.translationSnap; + } + + if (axis.search('Y') !== -1) { + newPosition.y = + Math.round(newPosition.y / this.translationSnap) * this.translationSnap; + } + + if (axis.search('Z') !== -1) { + newPosition.z = + Math.round(newPosition.z / this.translationSnap) * this.translationSnap; + } + + if (object.parent) { + newPosition.sub( + _tempVector.setFromMatrixPosition(object.parent.matrixWorld), + ); + } + } + } + } else if (mode === 'scale') { + if (axis.search('XYZ') !== -1) { + let d = this.pointEnd.length() / this.pointStart.length(); + + if (this.pointEnd.dot(this.pointStart) < 0) d *= -1; + + _tempVector2.set(d, d, d); + } else { + _tempVector.copy(this.pointStart); + _tempVector2.copy(this.pointEnd); + + _tempVector.applyQuaternion(this._worldQuaternionInv); + _tempVector2.applyQuaternion(this._worldQuaternionInv); + + _tempVector2.divide(_tempVector); + + if (axis.search('X') === -1) { + _tempVector2.x = 1; + } + + if (axis.search('Y') === -1) { + _tempVector2.y = 1; + } + + if (axis.search('Z') === -1) { + _tempVector2.z = 1; + } + } + + // Apply scale + + object.scale.copy(this._scaleStart).multiply(_tempVector2); + + if (this.scaleSnap) { + if (axis.search('X') !== -1) { + object.scale.x = + Math.round(object.scale.x / this.scaleSnap) * this.scaleSnap || + this.scaleSnap; + } + + if (axis.search('Y') !== -1) { + object.scale.y = + Math.round(object.scale.y / this.scaleSnap) * this.scaleSnap || + this.scaleSnap; + } + + if (axis.search('Z') !== -1) { + object.scale.z = + Math.round(object.scale.z / this.scaleSnap) * this.scaleSnap || + this.scaleSnap; + } + } + } else if (mode === 'rotate') { + this._offset.copy(this.pointEnd).sub(this.pointStart); + + const ROTATION_SPEED = + 20 / + this.worldPosition.distanceTo( + _tempVector.setFromMatrixPosition(this.camera.matrixWorld), + ); + + if (axis === 'E') { + this.rotationAxis.copy(this.eye); + this.rotationAngle = this.pointEnd.angleTo(this.pointStart); + + this._startNorm.copy(this.pointStart).normalize(); + this._endNorm.copy(this.pointEnd).normalize(); + + this.rotationAngle *= + this._endNorm.cross(this._startNorm).dot(this.eye) < 0 ? 1 : -1; + } else if (axis === 'XYZE') { + this.rotationAxis.copy(this._offset).cross(this.eye).normalize(); + this.rotationAngle = + this._offset.dot(_tempVector.copy(this.rotationAxis).cross(this.eye)) * + ROTATION_SPEED; + } else if (axis === 'X' || axis === 'Y' || axis === 'Z') { + this.rotationAxis.copy(_unit[axis]); + + _tempVector.copy(_unit[axis]); + + if (space === 'local') { + _tempVector.applyQuaternion(this.worldQuaternion); + } + + this.rotationAngle = + this._offset.dot(_tempVector.cross(this.eye).normalize()) * ROTATION_SPEED; + } + + // Apply rotation snap + + if (this.rotationSnap) + this.rotationAngle = + Math.round(this.rotationAngle / this.rotationSnap) * this.rotationSnap; + + // Apply rotate + if (space === 'local' && axis !== 'E' && axis !== 'XYZE') { + object.quaternion.copy(this._quaternionStart); + object.quaternion + .multiply( + _tempQuaternion.setFromAxisAngle(this.rotationAxis, this.rotationAngle), + ) + .normalize(); + } else { + this.rotationAxis.applyQuaternion(this._parentQuaternionInv); + object.quaternion.copy( + _tempQuaternion.setFromAxisAngle(this.rotationAxis, this.rotationAngle), + ); + object.quaternion.multiply(this._quaternionStart).normalize(); + } + } + + // object.position.copy(newPosition); + _objectChangeEvent.data = newPosition; + // _positionLast + + this.dispatchEvent(_changeEvent); + this.dispatchEvent(_objectChangeEvent); + } + + pointerUp(pointer) { + if (pointer.button !== 0) return; + + if (this.dragging && this.axis !== null) { + _mouseUpEvent.mode = this.mode; + this.dispatchEvent(_mouseUpEvent); + } + + this.dragging = false; + this.axis = null; + } + + dispose() { + this.domElement.removeEventListener('pointerdown', this._onPointerDown); + this.domElement.removeEventListener('pointermove', this._onPointerHover); + this.domElement.removeEventListener('pointermove', this._onPointerMove); + this.domElement.removeEventListener('pointerup', this._onPointerUp); + + this.traverse(function (child) { + if (child.geometry) child.geometry.dispose(); + if (child.material) child.material.dispose(); + }); + } + + // Set current object + attach(object) { + this.object = object; + this.visible = true; + + return this; + } + + // Detatch from object + detach() { + this.object = undefined; + this.visible = false; + this.axis = null; + + return this; + } + + reset() { + if (!this.enabled) return; + + if (this.dragging) { + this.object.position.copy(this._positionStart); + this.object.quaternion.copy(this._quaternionStart); + this.object.scale.copy(this._scaleStart); + + this.dispatchEvent(_changeEvent); + this.dispatchEvent(_objectChangeEvent); + + this.pointStart.copy(this.pointEnd); + } + } + + getRaycaster() { + return _raycaster; + } + + // TODO: deprecate + + getMode() { + return this.mode; + } + + setMode(mode) { + this.mode = mode; + } + + setTranslationSnap(translationSnap) { + this.translationSnap = translationSnap; + } + + setRotationSnap(rotationSnap) { + this.rotationSnap = rotationSnap; + } + + setScaleSnap(scaleSnap) { + this.scaleSnap = scaleSnap; + } + + setSize(size) { + this.size = size; + } + + setSpace(space) { + this.space = space; + } + + update() { + console.warn( + 'THREE.TransformControls: update function has no more functionality and therefore has been deprecated.', + ); + } +} + +TransformControls.prototype.isTransformControls = true; + +// mouse / touch event handlers + +function getPointer(event) { + if (this.domElement.ownerDocument.pointerLockElement) { + return { + x: 0, + y: 0, + button: event.button, + }; + } else { + const rect = this.domElement.getBoundingClientRect(); + + return { + x: ((event.clientX - rect.left) / rect.width) * 2 - 1, + y: (-(event.clientY - rect.top) / rect.height) * 2 + 1, + button: event.button, + }; + } +} + +function onPointerHover(event) { + if (!this.enabled) return; + + switch (event.pointerType) { + case 'mouse': + case 'pen': + this.pointerHover(this._getPointer(event)); + break; + } +} + +function onPointerDown(event) { + if (!this.enabled) return; + + if (!document.pointerLockElement) { + this.domElement.setPointerCapture(event.pointerId); + } + + this.domElement.addEventListener('pointermove', this._onPointerMove); + + this.pointerHover(this._getPointer(event)); + this.pointerDown(this._getPointer(event)); +} + +function onPointerMove(event) { + if (!this.enabled) return; + + this.pointerMove(this._getPointer(event)); +} + +function onPointerUp(event) { + if (!this.enabled) return; + + this.domElement.releasePointerCapture(event.pointerId); + + this.domElement.removeEventListener('pointermove', this._onPointerMove); + + this.pointerUp(this._getPointer(event)); +} + +function intersectObjectWithRay(object, raycaster, includeInvisible) { + const allIntersections = raycaster.intersectObject(object, true); + + for (let i = 0; i < allIntersections.length; i++) { + if (allIntersections[i].object.visible || includeInvisible) { + return allIntersections[i]; + } + } + + return false; +} + +// + +// Reusable utility variables + +const _tempEuler = new Euler(); +const _alignVector = new Vector3(0, 1, 0); +const _zeroVector = new Vector3(0, 0, 0); +const _lookAtMatrix = new Matrix4(); +const _tempQuaternion2 = new Quaternion(); +const _identityQuaternion = new Quaternion(); +const _dirVector = new Vector3(); +const _tempMatrix = new Matrix4(); + +const _unitX = new Vector3(1, 0, 0); +const _unitY = new Vector3(0, 1, 0); +const _unitZ = new Vector3(0, 0, 1); + +const _v1 = new Vector3(); +const _v2 = new Vector3(); +const _v3 = new Vector3(); + +class TransformControlsGizmo extends Object3D { + constructor() { + super(); + + this.type = 'TransformControlsGizmo'; + + // shared materials + + const gizmoMaterial = new MeshBasicMaterial({ + depthTest: false, + depthWrite: false, + fog: false, + toneMapped: false, + transparent: true, + }); + + const gizmoLineMaterial = new LineBasicMaterial({ + depthTest: false, + depthWrite: false, + fog: false, + toneMapped: false, + transparent: true, + }); + + // Make unique material for each axis/color + + const matInvisible = gizmoMaterial.clone(); + matInvisible.opacity = 0.15; + + const matHelper = gizmoLineMaterial.clone(); + matHelper.opacity = 0.5; + + const matRed = gizmoMaterial.clone(); + matRed.color.setHex(0xff0000); + + const matGreen = gizmoMaterial.clone(); + matGreen.color.setHex(0x00ff00); + + const matBlue = gizmoMaterial.clone(); + matBlue.color.setHex(0x0000ff); + + const matRedTransparent = gizmoMaterial.clone(); + matRedTransparent.color.setHex(0xff0000); + matRedTransparent.opacity = 0.5; + + const matGreenTransparent = gizmoMaterial.clone(); + matGreenTransparent.color.setHex(0x00ff00); + matGreenTransparent.opacity = 0.5; + + const matBlueTransparent = gizmoMaterial.clone(); + matBlueTransparent.color.setHex(0x0000ff); + matBlueTransparent.opacity = 0.5; + + const matWhiteTransparent = gizmoMaterial.clone(); + matWhiteTransparent.opacity = 0.25; + + const matYellowTransparent = gizmoMaterial.clone(); + matYellowTransparent.color.setHex(0xffff00); + matYellowTransparent.opacity = 0.25; + + const matYellow = gizmoMaterial.clone(); + matYellow.color.setHex(0xffff00); + + const matGray = gizmoMaterial.clone(); + matGray.color.setHex(0x787878); + + // reusable geometry + + const arrowGeometry = new CylinderGeometry(0, 0.04, 0.1, 12); + arrowGeometry.translate(0, 0.05, 0); + + const scaleHandleGeometry = new BoxGeometry(0.08, 0.08, 0.08); + scaleHandleGeometry.translate(0, 0.04, 0); + + const lineGeometry = new BufferGeometry(); + lineGeometry.setAttribute('position', new Float32BufferAttribute([0, 0, 0, 1, 0, 0], 3)); + + const lineGeometry2 = new CylinderGeometry(0.0075, 0.0075, 0.5, 3); + lineGeometry2.translate(0, 0.25, 0); + + function CircleGeometry(radius, arc) { + const geometry = new TorusGeometry(radius, 0.0075, 3, 64, arc * Math.PI * 2); + geometry.rotateY(Math.PI / 2); + geometry.rotateX(Math.PI / 2); + return geometry; + } + + // Special geometry for transform helper. If scaled with position vector it spans from [0,0,0] to position + + function TranslateHelperGeometry() { + const geometry = new BufferGeometry(); + + geometry.setAttribute('position', new Float32BufferAttribute([0, 0, 0, 1, 1, 1], 3)); + + return geometry; + } + + // Gizmo definitions - custom hierarchy definitions for setupGizmo() function + + const gizmoTranslate = { + X: [ + [new Mesh(arrowGeometry, matRed), [0.5, 0, 0], [0, 0, -Math.PI / 2]], + [new Mesh(arrowGeometry, matRed), [-0.5, 0, 0], [0, 0, Math.PI / 2]], + [new Mesh(lineGeometry2, matRed), [0, 0, 0], [0, 0, -Math.PI / 2]], + ], + Y: [ + [new Mesh(arrowGeometry, matGreen), [0, 0.5, 0]], + [new Mesh(arrowGeometry, matGreen), [0, -0.5, 0], [Math.PI, 0, 0]], + [new Mesh(lineGeometry2, matGreen)], + ], + Z: [ + [new Mesh(arrowGeometry, matBlue), [0, 0, 0.5], [Math.PI / 2, 0, 0]], + [new Mesh(arrowGeometry, matBlue), [0, 0, -0.5], [-Math.PI / 2, 0, 0]], + [new Mesh(lineGeometry2, matBlue), null, [Math.PI / 2, 0, 0]], + ], + XYZ: [ + [new Mesh(new OctahedronGeometry(0.1, 0), matWhiteTransparent.clone()), [0, 0, 0]], + ], + XY: [ + [ + new Mesh(new BoxGeometry(0.15, 0.15, 0.01), matBlueTransparent.clone()), + [0.15, 0.15, 0], + ], + ], + YZ: [ + [ + new Mesh(new BoxGeometry(0.15, 0.15, 0.01), matRedTransparent.clone()), + [0, 0.15, 0.15], + [0, Math.PI / 2, 0], + ], + ], + XZ: [ + [ + new Mesh(new BoxGeometry(0.15, 0.15, 0.01), matGreenTransparent.clone()), + [0.15, 0, 0.15], + [-Math.PI / 2, 0, 0], + ], + ], + }; + + const pickerTranslate = { + X: [ + [ + new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), + [0.3, 0, 0], + [0, 0, -Math.PI / 2], + ], + [ + new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), + [-0.3, 0, 0], + [0, 0, Math.PI / 2], + ], + ], + Y: [ + [new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), [0, 0.3, 0]], + [ + new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), + [0, -0.3, 0], + [0, 0, Math.PI], + ], + ], + Z: [ + [ + new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), + [0, 0, 0.3], + [Math.PI / 2, 0, 0], + ], + [ + new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), + [0, 0, -0.3], + [-Math.PI / 2, 0, 0], + ], + ], + XYZ: [[new Mesh(new OctahedronGeometry(0.2, 0), matInvisible)]], + XY: [[new Mesh(new BoxGeometry(0.2, 0.2, 0.01), matInvisible), [0.15, 0.15, 0]]], + YZ: [ + [ + new Mesh(new BoxGeometry(0.2, 0.2, 0.01), matInvisible), + [0, 0.15, 0.15], + [0, Math.PI / 2, 0], + ], + ], + XZ: [ + [ + new Mesh(new BoxGeometry(0.2, 0.2, 0.01), matInvisible), + [0.15, 0, 0.15], + [-Math.PI / 2, 0, 0], + ], + ], + }; + + const helperTranslate = { + START: [ + [new Mesh(new OctahedronGeometry(0.01, 2), matHelper), null, null, null, 'helper'], + ], + END: [ + [new Mesh(new OctahedronGeometry(0.01, 2), matHelper), null, null, null, 'helper'], + ], + DELTA: [[new Line(TranslateHelperGeometry(), matHelper), null, null, null, 'helper']], + X: [ + [ + new Line(lineGeometry, matHelper.clone()), + [-1e3, 0, 0], + null, + [1e6, 1, 1], + 'helper', + ], + ], + Y: [ + [ + new Line(lineGeometry, matHelper.clone()), + [0, -1e3, 0], + [0, 0, Math.PI / 2], + [1e6, 1, 1], + 'helper', + ], + ], + Z: [ + [ + new Line(lineGeometry, matHelper.clone()), + [0, 0, -1e3], + [0, -Math.PI / 2, 0], + [1e6, 1, 1], + 'helper', + ], + ], + }; + + const gizmoRotate = { + XYZE: [[new Mesh(CircleGeometry(0.5, 1), matGray), null, [0, Math.PI / 2, 0]]], + X: [[new Mesh(CircleGeometry(0.5, 0.5), matRed)]], + Y: [[new Mesh(CircleGeometry(0.5, 0.5), matGreen), null, [0, 0, -Math.PI / 2]]], + Z: [[new Mesh(CircleGeometry(0.5, 0.5), matBlue), null, [0, Math.PI / 2, 0]]], + E: [ + [ + new Mesh(CircleGeometry(0.75, 1), matYellowTransparent), + null, + [0, Math.PI / 2, 0], + ], + ], + }; + + const helperRotate = { + AXIS: [ + [ + new Line(lineGeometry, matHelper.clone()), + [-1e3, 0, 0], + null, + [1e6, 1, 1], + 'helper', + ], + ], + }; + + const pickerRotate = { + XYZE: [[new Mesh(new SphereGeometry(0.25, 10, 8), matInvisible)]], + X: [ + [ + new Mesh(new TorusGeometry(0.5, 0.1, 4, 24), matInvisible), + [0, 0, 0], + [0, -Math.PI / 2, -Math.PI / 2], + ], + ], + Y: [ + [ + new Mesh(new TorusGeometry(0.5, 0.1, 4, 24), matInvisible), + [0, 0, 0], + [Math.PI / 2, 0, 0], + ], + ], + Z: [ + [ + new Mesh(new TorusGeometry(0.5, 0.1, 4, 24), matInvisible), + [0, 0, 0], + [0, 0, -Math.PI / 2], + ], + ], + E: [[new Mesh(new TorusGeometry(0.75, 0.1, 2, 24), matInvisible)]], + }; + + const gizmoScale = { + X: [ + [new Mesh(scaleHandleGeometry, matRed), [0.5, 0, 0], [0, 0, -Math.PI / 2]], + [new Mesh(lineGeometry2, matRed), [0, 0, 0], [0, 0, -Math.PI / 2]], + [new Mesh(scaleHandleGeometry, matRed), [-0.5, 0, 0], [0, 0, Math.PI / 2]], + ], + Y: [ + [new Mesh(scaleHandleGeometry, matGreen), [0, 0.5, 0]], + [new Mesh(lineGeometry2, matGreen)], + [new Mesh(scaleHandleGeometry, matGreen), [0, -0.5, 0], [0, 0, Math.PI]], + ], + Z: [ + [new Mesh(scaleHandleGeometry, matBlue), [0, 0, 0.5], [Math.PI / 2, 0, 0]], + [new Mesh(lineGeometry2, matBlue), [0, 0, 0], [Math.PI / 2, 0, 0]], + [new Mesh(scaleHandleGeometry, matBlue), [0, 0, -0.5], [-Math.PI / 2, 0, 0]], + ], + XY: [ + [new Mesh(new BoxGeometry(0.15, 0.15, 0.01), matBlueTransparent), [0.15, 0.15, 0]], + ], + YZ: [ + [ + new Mesh(new BoxGeometry(0.15, 0.15, 0.01), matRedTransparent), + [0, 0.15, 0.15], + [0, Math.PI / 2, 0], + ], + ], + XZ: [ + [ + new Mesh(new BoxGeometry(0.15, 0.15, 0.01), matGreenTransparent), + [0.15, 0, 0.15], + [-Math.PI / 2, 0, 0], + ], + ], + XYZ: [[new Mesh(new BoxGeometry(0.1, 0.1, 0.1), matWhiteTransparent.clone())]], + }; + + const pickerScale = { + X: [ + [ + new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), + [0.3, 0, 0], + [0, 0, -Math.PI / 2], + ], + [ + new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), + [-0.3, 0, 0], + [0, 0, Math.PI / 2], + ], + ], + Y: [ + [new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), [0, 0.3, 0]], + [ + new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), + [0, -0.3, 0], + [0, 0, Math.PI], + ], + ], + Z: [ + [ + new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), + [0, 0, 0.3], + [Math.PI / 2, 0, 0], + ], + [ + new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), + [0, 0, -0.3], + [-Math.PI / 2, 0, 0], + ], + ], + XY: [[new Mesh(new BoxGeometry(0.2, 0.2, 0.01), matInvisible), [0.15, 0.15, 0]]], + YZ: [ + [ + new Mesh(new BoxGeometry(0.2, 0.2, 0.01), matInvisible), + [0, 0.15, 0.15], + [0, Math.PI / 2, 0], + ], + ], + XZ: [ + [ + new Mesh(new BoxGeometry(0.2, 0.2, 0.01), matInvisible), + [0.15, 0, 0.15], + [-Math.PI / 2, 0, 0], + ], + ], + XYZ: [[new Mesh(new BoxGeometry(0.2, 0.2, 0.2), matInvisible), [0, 0, 0]]], + }; + + const helperScale = { + X: [ + [ + new Line(lineGeometry, matHelper.clone()), + [-1e3, 0, 0], + null, + [1e6, 1, 1], + 'helper', + ], + ], + Y: [ + [ + new Line(lineGeometry, matHelper.clone()), + [0, -1e3, 0], + [0, 0, Math.PI / 2], + [1e6, 1, 1], + 'helper', + ], + ], + Z: [ + [ + new Line(lineGeometry, matHelper.clone()), + [0, 0, -1e3], + [0, -Math.PI / 2, 0], + [1e6, 1, 1], + 'helper', + ], + ], + }; + + // Creates an Object3D with gizmos described in custom hierarchy definition. + + function setupGizmo(gizmoMap) { + const gizmo = new Object3D(); + + for (const name in gizmoMap) { + for (let i = gizmoMap[name].length; i--; ) { + const object = gizmoMap[name][i][0].clone(); + const position = gizmoMap[name][i][1]; + const rotation = gizmoMap[name][i][2]; + const scale = gizmoMap[name][i][3]; + const tag = gizmoMap[name][i][4]; + + // name and tag properties are essential for picking and updating logic. + object.name = name; + object.tag = tag; + + if (position) { + object.position.set(position[0], position[1], position[2]); + } + + if (rotation) { + object.rotation.set(rotation[0], rotation[1], rotation[2]); + } + + if (scale) { + object.scale.set(scale[0], scale[1], scale[2]); + } + + object.updateMatrix(); + + const tempGeometry = object.geometry.clone(); + tempGeometry.applyMatrix4(object.matrix); + object.geometry = tempGeometry; + object.renderOrder = Infinity; + + object.position.set(0, 0, 0); + object.rotation.set(0, 0, 0); + object.scale.set(1, 1, 1); + + gizmo.add(object); + } + } + + return gizmo; + } + + // Gizmo creation + + this.gizmo = {}; + this.picker = {}; + this.helper = {}; + + this.add((this.gizmo['translate'] = setupGizmo(gizmoTranslate))); + this.add((this.gizmo['rotate'] = setupGizmo(gizmoRotate))); + this.add((this.gizmo['scale'] = setupGizmo(gizmoScale))); + this.add((this.picker['translate'] = setupGizmo(pickerTranslate))); + this.add((this.picker['rotate'] = setupGizmo(pickerRotate))); + this.add((this.picker['scale'] = setupGizmo(pickerScale))); + this.add((this.helper['translate'] = setupGizmo(helperTranslate))); + this.add((this.helper['rotate'] = setupGizmo(helperRotate))); + this.add((this.helper['scale'] = setupGizmo(helperScale))); + + // Pickers should be hidden always + + this.picker['translate'].visible = false; + this.picker['rotate'].visible = false; + this.picker['scale'].visible = false; + } + + // updateMatrixWorld will update transformations and appearance of individual handles + + updateMatrixWorld(force) { + const space = this.mode === 'scale' ? 'local' : this.space; // scale always oriented to local rotation + + const quaternion = space === 'local' ? this.worldQuaternion : _identityQuaternion; + + // Show only gizmos for current transform mode + + this.gizmo['translate'].visible = this.mode === 'translate'; + this.gizmo['rotate'].visible = this.mode === 'rotate'; + this.gizmo['scale'].visible = this.mode === 'scale'; + + this.helper['translate'].visible = this.mode === 'translate'; + this.helper['rotate'].visible = this.mode === 'rotate'; + this.helper['scale'].visible = this.mode === 'scale'; + + let handles = []; + handles = handles.concat(this.picker[this.mode].children); + handles = handles.concat(this.gizmo[this.mode].children); + handles = handles.concat(this.helper[this.mode].children); + + for (let i = 0; i < handles.length; i++) { + const handle = handles[i]; + + // hide aligned to camera + + handle.visible = true; + handle.rotation.set(0, 0, 0); + handle.position.copy(this.worldPosition); + + let factor; + + if (this.camera.isOrthographicCamera) { + factor = (this.camera.top - this.camera.bottom) / this.camera.zoom; + } else { + factor = + this.worldPosition.distanceTo(this.cameraPosition) * + Math.min( + (1.9 * Math.tan((Math.PI * this.camera.fov) / 360)) / this.camera.zoom, + 7, + ); + } + + handle.scale.set(1, 1, 1).multiplyScalar((factor * this.size) / 4); + + // TODO: simplify helpers and consider decoupling from gizmo + + if (handle.tag === 'helper') { + handle.visible = false; + + if (handle.name === 'AXIS') { + handle.position.copy(this.worldPositionStart); + handle.visible = !!this.axis; + + if (this.axis === 'X') { + _tempQuaternion.setFromEuler(_tempEuler.set(0, 0, 0)); + handle.quaternion.copy(quaternion).multiply(_tempQuaternion); + + if ( + Math.abs( + _alignVector.copy(_unitX).applyQuaternion(quaternion).dot(this.eye), + ) > 0.9 + ) { + handle.visible = false; + } + } + + if (this.axis === 'Y') { + _tempQuaternion.setFromEuler(_tempEuler.set(0, 0, Math.PI / 2)); + handle.quaternion.copy(quaternion).multiply(_tempQuaternion); + + if ( + Math.abs( + _alignVector.copy(_unitY).applyQuaternion(quaternion).dot(this.eye), + ) > 0.9 + ) { + handle.visible = false; + } + } + + if (this.axis === 'Z') { + _tempQuaternion.setFromEuler(_tempEuler.set(0, Math.PI / 2, 0)); + handle.quaternion.copy(quaternion).multiply(_tempQuaternion); + + if ( + Math.abs( + _alignVector.copy(_unitZ).applyQuaternion(quaternion).dot(this.eye), + ) > 0.9 + ) { + handle.visible = false; + } + } + + if (this.axis === 'XYZE') { + _tempQuaternion.setFromEuler(_tempEuler.set(0, Math.PI / 2, 0)); + _alignVector.copy(this.rotationAxis); + handle.quaternion.setFromRotationMatrix( + _lookAtMatrix.lookAt(_zeroVector, _alignVector, _unitY), + ); + handle.quaternion.multiply(_tempQuaternion); + handle.visible = this.dragging; + } + + if (this.axis === 'E') { + handle.visible = false; + } + } else if (handle.name === 'START') { + handle.position.copy(this.worldPositionStart); + handle.visible = this.dragging; + } else if (handle.name === 'END') { + handle.position.copy(this.worldPosition); + handle.visible = this.dragging; + } else if (handle.name === 'DELTA') { + handle.position.copy(this.worldPositionStart); + handle.quaternion.copy(this.worldQuaternionStart); + _tempVector + .set(1e-10, 1e-10, 1e-10) + .add(this.worldPositionStart) + .sub(this.worldPosition) + .multiplyScalar(-1); + _tempVector.applyQuaternion(this.worldQuaternionStart.clone().invert()); + handle.scale.copy(_tempVector); + handle.visible = this.dragging; + } else { + handle.quaternion.copy(quaternion); + + if (this.dragging) { + handle.position.copy(this.worldPositionStart); + } else { + handle.position.copy(this.worldPosition); + } + + if (this.axis) { + handle.visible = this.axis.search(handle.name) !== -1; + } + } + + // If updating helper, skip rest of the loop + continue; + } + + // Align handles to current local or world rotation + + handle.quaternion.copy(quaternion); + + if (this.mode === 'translate' || this.mode === 'scale') { + // Hide translate and scale axis facing the camera + + const AXIS_HIDE_TRESHOLD = 0.99; + const PLANE_HIDE_TRESHOLD = 0.2; + + if (handle.name === 'X') { + if ( + Math.abs( + _alignVector.copy(_unitX).applyQuaternion(quaternion).dot(this.eye), + ) > AXIS_HIDE_TRESHOLD + ) { + handle.scale.set(1e-10, 1e-10, 1e-10); + handle.visible = false; + } + } + + if (handle.name === 'Y') { + if ( + Math.abs( + _alignVector.copy(_unitY).applyQuaternion(quaternion).dot(this.eye), + ) > AXIS_HIDE_TRESHOLD + ) { + handle.scale.set(1e-10, 1e-10, 1e-10); + handle.visible = false; + } + } + + if (handle.name === 'Z') { + if ( + Math.abs( + _alignVector.copy(_unitZ).applyQuaternion(quaternion).dot(this.eye), + ) > AXIS_HIDE_TRESHOLD + ) { + handle.scale.set(1e-10, 1e-10, 1e-10); + handle.visible = false; + } + } + + if (handle.name === 'XY') { + if ( + Math.abs( + _alignVector.copy(_unitZ).applyQuaternion(quaternion).dot(this.eye), + ) < PLANE_HIDE_TRESHOLD + ) { + handle.scale.set(1e-10, 1e-10, 1e-10); + handle.visible = false; + } + } + + if (handle.name === 'YZ') { + if ( + Math.abs( + _alignVector.copy(_unitX).applyQuaternion(quaternion).dot(this.eye), + ) < PLANE_HIDE_TRESHOLD + ) { + handle.scale.set(1e-10, 1e-10, 1e-10); + handle.visible = false; + } + } + + if (handle.name === 'XZ') { + if ( + Math.abs( + _alignVector.copy(_unitY).applyQuaternion(quaternion).dot(this.eye), + ) < PLANE_HIDE_TRESHOLD + ) { + handle.scale.set(1e-10, 1e-10, 1e-10); + handle.visible = false; + } + } + } else if (this.mode === 'rotate') { + // Align handles to current local or world rotation + + _tempQuaternion2.copy(quaternion); + _alignVector + .copy(this.eye) + .applyQuaternion(_tempQuaternion.copy(quaternion).invert()); + + if (handle.name.search('E') !== -1) { + handle.quaternion.setFromRotationMatrix( + _lookAtMatrix.lookAt(this.eye, _zeroVector, _unitY), + ); + } + + if (handle.name === 'X') { + _tempQuaternion.setFromAxisAngle( + _unitX, + Math.atan2(-_alignVector.y, _alignVector.z), + ); + _tempQuaternion.multiplyQuaternions(_tempQuaternion2, _tempQuaternion); + handle.quaternion.copy(_tempQuaternion); + } + + if (handle.name === 'Y') { + _tempQuaternion.setFromAxisAngle( + _unitY, + Math.atan2(_alignVector.x, _alignVector.z), + ); + _tempQuaternion.multiplyQuaternions(_tempQuaternion2, _tempQuaternion); + handle.quaternion.copy(_tempQuaternion); + } + + if (handle.name === 'Z') { + _tempQuaternion.setFromAxisAngle( + _unitZ, + Math.atan2(_alignVector.y, _alignVector.x), + ); + _tempQuaternion.multiplyQuaternions(_tempQuaternion2, _tempQuaternion); + handle.quaternion.copy(_tempQuaternion); + } + } + + // Hide disabled axes + handle.visible = handle.visible && (handle.name.indexOf('X') === -1 || this.showX); + handle.visible = handle.visible && (handle.name.indexOf('Y') === -1 || this.showY); + handle.visible = handle.visible && (handle.name.indexOf('Z') === -1 || this.showZ); + handle.visible = + handle.visible && + (handle.name.indexOf('E') === -1 || (this.showX && this.showY && this.showZ)); + + // highlight selected axis + + handle.material._color = handle.material._color || handle.material.color.clone(); + handle.material._opacity = handle.material._opacity || handle.material.opacity; + + handle.material.color.copy(handle.material._color); + handle.material.opacity = handle.material._opacity; + + if (this.enabled && this.axis) { + if (handle.name === this.axis) { + handle.material.color.setHex(0xffff00); + handle.material.opacity = 1.0; + } else if ( + this.axis.split('').some(function (a) { + return handle.name === a; + }) + ) { + handle.material.color.setHex(0xffff00); + handle.material.opacity = 1.0; + } + } + } + + super.updateMatrixWorld(force); + } +} + +TransformControlsGizmo.prototype.isTransformControlsGizmo = true; + +// + +class TransformControlsPlane extends Mesh { + constructor() { + super( + new PlaneGeometry(100000, 100000, 2, 2), + new MeshBasicMaterial({ + visible: false, + wireframe: true, + side: DoubleSide, + transparent: true, + opacity: 0.1, + toneMapped: false, + }), + ); + + this.type = 'TransformControlsPlane'; + } + + updateMatrixWorld(force) { + let space = this.space; + + this.position.copy(this.worldPosition); + + if (this.mode === 'scale') space = 'local'; // scale always oriented to local rotation + + _v1.copy(_unitX).applyQuaternion( + space === 'local' ? this.worldQuaternion : _identityQuaternion, + ); + _v2.copy(_unitY).applyQuaternion( + space === 'local' ? this.worldQuaternion : _identityQuaternion, + ); + _v3.copy(_unitZ).applyQuaternion( + space === 'local' ? this.worldQuaternion : _identityQuaternion, + ); + + // Align the plane for current transform mode, axis and space. + + _alignVector.copy(_v2); + + switch (this.mode) { + case 'translate': + case 'scale': + switch (this.axis) { + case 'X': + _alignVector.copy(this.eye).cross(_v1); + _dirVector.copy(_v1).cross(_alignVector); + break; + case 'Y': + _alignVector.copy(this.eye).cross(_v2); + _dirVector.copy(_v2).cross(_alignVector); + break; + case 'Z': + _alignVector.copy(this.eye).cross(_v3); + _dirVector.copy(_v3).cross(_alignVector); + break; + case 'XY': + _dirVector.copy(_v3); + break; + case 'YZ': + _dirVector.copy(_v1); + break; + case 'XZ': + _alignVector.copy(_v3); + _dirVector.copy(_v2); + break; + case 'XYZ': + case 'E': + _dirVector.set(0, 0, 0); + break; + } + + break; + case 'rotate': + default: + // special case for rotate + _dirVector.set(0, 0, 0); + } + + if (_dirVector.length() === 0) { + // If in rotate mode, make the plane parallel to camera + this.quaternion.copy(this.cameraQuaternion); + } else { + _tempMatrix.lookAt(_tempVector.set(0, 0, 0), _dirVector, _alignVector); + + this.quaternion.setFromRotationMatrix(_tempMatrix); + } + + super.updateMatrixWorld(force); + } +} + +TransformControlsPlane.prototype.isTransformControlsPlane = true; + +export { TransformControls, TransformControlsGizmo, TransformControlsPlane }; diff --git a/frontend/text-tool/src/packages/pc-render/common/ViewHelper.ts b/frontend/text-tool/src/packages/pc-render/common/ViewHelper.ts new file mode 100644 index 00000000..348677bb --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/common/ViewHelper.ts @@ -0,0 +1,419 @@ +import * as THREE from 'three'; +import TWEEN, { Tween } from '@tweenjs/tween.js'; +import MainRenderView from '../renderView/MainRenderView'; +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; + +const vpTemp = new THREE.Vector4(); + +const viewPosition = { + posX: 'posX', + posY: 'posY', + posZ: 'posZ', + negX: 'negX', + negY: 'negY', + negZ: 'negZ', +}; +export type viewType = keyof typeof viewPosition; + +export class UIElement { + dom: HTMLDivElement; + constructor(dom: HTMLDivElement) { + this.dom = dom; + } + + add(...args: any) { + for (let i = 0; i < arguments.length; i++) { + const argument = args[i]; + + if (argument instanceof UIElement) { + this.dom.appendChild(argument.dom); + } else { + console.error('UIElement:', argument, 'is not an instance of UIElement.'); + } + } + + return this; + } + + remove(...args: any) { + for (let i = 0; i < arguments.length; i++) { + const argument = args[i]; + + if (argument instanceof UIElement) { + this.dom.removeChild(argument.dom); + } else { + console.error('UIElement:', argument, 'is not an instance of UIElement.'); + } + } + + return this; + } + + setId(id: string) { + this.dom.id = id; + + return this; + } + + getId() { + return this.dom.id; + } + + setClass(name: string) { + this.dom.className = name; + + return this; + } + + addClass(name: string) { + this.dom.classList.add(name); + + return this; + } + + removeClass(name: string) { + this.dom.classList.remove(name); + + return this; + } + + setStyle(style: string, array: any[]) { + for (let i = 0; i < array.length; i++) { + this.dom.style[style] = array[i]; + } + + return this; + } + + setTextContent(value: any) { + this.dom.textContent = value; + + return this; + } + + setInnerHTML(value: any) { + this.dom.innerHTML = value; + } + + getIndexOfChild(element: UIElement) { + return Array.prototype.indexOf.call(this.dom.children, element.dom); + } +} + +export class ViewHelper extends THREE.Object3D { + controls: OrbitControls; + renderView: MainRenderView; + isViewHelper: boolean = true; + camera: THREE.OrthographicCamera; + private raycaster: THREE.Raycaster; + private mouse: THREE.Vector2; + private dummy: THREE.Object3D; + private clock: THREE.Clock; + + private posXAxisHelper: THREE.Sprite; + private posYAxisHelper: THREE.Sprite; + private posZAxisHelper: THREE.Sprite; + private negXAxisHelper: THREE.Sprite; + private negYAxisHelper: THREE.Sprite; + private negZAxisHelper: THREE.Sprite; + private point: THREE.Vector3; + private dim: number = 128; + private turnRate: number = 2 * Math.PI; + private radius: number = 0; + private targetPosition: THREE.Vector3; + private targetQuaternion: THREE.Quaternion; + private q1: THREE.Quaternion; + private q2: THREE.Quaternion; + private m: THREE.Matrix4; + private offset: THREE.Vector3; + private quat: THREE.Quaternion; + private quatInverse: THREE.Quaternion; + private spherical: THREE.Spherical; + private interactiveObjects: THREE.Sprite[]; + + constructor(renderView: MainRenderView, controls: OrbitControls) { + super(); + this.controls = controls; + this.renderView = renderView; + + const color1 = new THREE.Color('#ff3653'); + const color2 = new THREE.Color('#8adb00'); + const color3 = new THREE.Color('#2c8fff'); + + const interactiveObjects = []; + this.raycaster = new THREE.Raycaster(); + this.mouse = new THREE.Vector2(); + + this.dummy = new THREE.Object3D(); + this.dummy.up.copy(this.renderView.camera.up); + + this.camera = new THREE.OrthographicCamera(-2, 2, 2, -2, 0, 4); + this.camera.position.set(0, 0, 2); + this.clock = new THREE.Clock(); + + this.m = new THREE.Matrix4(); + + const geometry = new THREE.BoxGeometry(0.8, 0.05, 0.05).translate(0.4, 0, 0); + + const xAxis = new THREE.Mesh(geometry, getAxisMaterial(color1)); + const yAxis = new THREE.Mesh(geometry, getAxisMaterial(color2)); + const zAxis = new THREE.Mesh(geometry, getAxisMaterial(color3)); + + yAxis.rotation.z = Math.PI / 2; + zAxis.rotation.y = -Math.PI / 2; + + this.add(xAxis); + this.add(zAxis); + this.add(yAxis); + + const posXAxisHelper = new THREE.Sprite(getSpriteMaterial(color1, 'X')); + posXAxisHelper.userData.type = viewPosition.posX; + const posYAxisHelper = new THREE.Sprite(getSpriteMaterial(color2, 'Y')); + posYAxisHelper.userData.type = viewPosition.posY; + const posZAxisHelper = new THREE.Sprite(getSpriteMaterial(color3, 'Z')); + posZAxisHelper.userData.type = viewPosition.posZ; + const negXAxisHelper = new THREE.Sprite(getSpriteMaterial(color1)); + negXAxisHelper.userData.type = viewPosition.negX; + const negYAxisHelper = new THREE.Sprite(getSpriteMaterial(color2)); + negYAxisHelper.userData.type = viewPosition.negY; + const negZAxisHelper = new THREE.Sprite(getSpriteMaterial(color3)); + negZAxisHelper.userData.type = viewPosition.negZ; + + posXAxisHelper.position.x = 1; + posYAxisHelper.position.y = 1; + posZAxisHelper.position.z = 1; + negXAxisHelper.position.x = -1; + negXAxisHelper.scale.setScalar(0.8); + negYAxisHelper.position.y = -1; + negYAxisHelper.scale.setScalar(0.8); + negZAxisHelper.position.z = -1; + negZAxisHelper.scale.setScalar(0.8); + + this.add(posXAxisHelper); + this.add(posYAxisHelper); + this.add(posZAxisHelper); + this.add(negXAxisHelper); + this.add(negYAxisHelper); + this.add(negZAxisHelper); + this.posXAxisHelper = posXAxisHelper; + this.posYAxisHelper = posYAxisHelper; + this.posZAxisHelper = posZAxisHelper; + this.negXAxisHelper = negXAxisHelper; + this.negYAxisHelper = negYAxisHelper; + this.negZAxisHelper = negZAxisHelper; + + interactiveObjects.push(posXAxisHelper); + interactiveObjects.push(posYAxisHelper); + interactiveObjects.push(posZAxisHelper); + interactiveObjects.push(negXAxisHelper); + interactiveObjects.push(negYAxisHelper); + interactiveObjects.push(negZAxisHelper); + this.interactiveObjects = interactiveObjects; + this.point = new THREE.Vector3(); + this.dim = 128; + this.turnRate = 2 * Math.PI; // turn rate in angles per second + + this.targetPosition = new THREE.Vector3(); + this.targetQuaternion = new THREE.Quaternion(); + + this.q1 = new THREE.Quaternion(); + this.q2 = new THREE.Quaternion(); + + this.offset = new THREE.Vector3(); + this.quat = new THREE.Quaternion().setFromUnitVectors( + this.renderView.camera.up, + new THREE.Vector3(0, 1, 0), + ); + this.quatInverse = this.quat.clone().invert(); + this.spherical = new THREE.Spherical(); + this.radius = 0; + + function getAxisMaterial(color: THREE.Color) { + return new THREE.MeshBasicMaterial({ color: color, toneMapped: false }); + } + + function getSpriteMaterial(color: THREE.Color, text: string | null = null) { + const canvas = document.createElement('canvas'); + canvas.width = 64; + canvas.height = 64; + + const context: CanvasRenderingContext2D = canvas.getContext( + '2d', + ) as CanvasRenderingContext2D; + context.beginPath(); + context.arc(32, 32, 16, 0, 2 * Math.PI); + context.closePath(); + context.fillStyle = color.getStyle(); + context.fill(); + + if (text !== null) { + context.font = '24px Arial'; + context.textAlign = 'center'; + context.fillStyle = '#000000'; + context.fillText(text, 32, 41); + } + + const texture = new THREE.CanvasTexture(canvas); + + return new THREE.SpriteMaterial({ + map: texture, + // depthWrite: false, + // transparent: true, + depthTest: false, + toneMapped: true, + }); + } + } + render(renderer: THREE.WebGLRenderer) { + this.quaternion.copy(this.renderView.camera.quaternion).invert(); + this.updateMatrixWorld(); + + this.point.set(0, 0, 1); + this.point.applyQuaternion(this.renderView.camera.quaternion); + + if (this.point.x >= 0) { + this.posXAxisHelper.material.opacity = 1; + this.negXAxisHelper.material.opacity = 0.5; + } else { + this.posXAxisHelper.material.opacity = 0.5; + this.negXAxisHelper.material.opacity = 1; + } + + if (this.point.y >= 0) { + this.posYAxisHelper.material.opacity = 1; + this.negYAxisHelper.material.opacity = 0.5; + } else { + this.posYAxisHelper.material.opacity = 0.5; + this.negYAxisHelper.material.opacity = 1; + } + + if (this.point.z >= 0) { + this.posZAxisHelper.material.opacity = 1; + this.negZAxisHelper.material.opacity = 0.5; + } else { + this.posZAxisHelper.material.opacity = 0.5; + this.negZAxisHelper.material.opacity = 1; + } + + // + + const x = this.renderView.container.offsetWidth - this.dim; + + renderer.clearDepth(); + + renderer.getViewport(vpTemp); + renderer.setViewport(x, 0, this.dim, this.dim); + + renderer.render(this, this.camera); + + renderer.setViewport(vpTemp.x, vpTemp.y, vpTemp.z, vpTemp.w); + } + handleClick(event: MouseEvent) { + this.mouse.x = (event.offsetX / this.dim) * 2 - 1; + this.mouse.y = (-event.offsetY / this.dim) * 2 + 1; + + this.raycaster.setFromCamera(this.mouse, this.camera); + + const intersects = this.raycaster.intersectObjects(this.interactiveObjects); + if (intersects.length > 0) { + const intersection = intersects[0]; + const object = intersection.object; + this.view(object.userData.type); + return true; + } else { + return false; + } + } + view(type: viewType): Promise { + return new Promise((resolve, reject) => { + const focusPoint = this.controls.target; + const _camera = this.renderView.camera; + switch (type) { + case viewPosition.posX: + this.targetPosition.set(1, 0, 0); + break; + case viewPosition.posY: + this.targetPosition.set(0, 1, 0); + break; + case viewPosition.posZ: + this.targetPosition.set(0, 0, 1); + break; + case viewPosition.negX: + this.targetPosition.set(-1, 0, 0); + break; + case viewPosition.negY: + this.targetPosition.set(0, -1, 0); + break; + case viewPosition.negZ: + this.targetPosition.set(0, 0, -1); + break; + + default: + console.error('ViewHelper: Invalid axis.'); + } + this.offset.copy(this.targetPosition); + this.offset.applyQuaternion(this.quat); + this.spherical.setFromVector3(this.offset); + this.spherical.phi = Math.max( + this.controls.minPolarAngle, + Math.min(this.controls.maxPolarAngle, this.spherical.phi), + ); + this.spherical.makeSafe(); + this.offset.setFromSpherical(this.spherical); + this.offset.applyQuaternion(this.quatInverse); + this.targetPosition.set(0, 0, 0).add(this.offset); + this.m.lookAt(this.targetPosition, new THREE.Vector3(0, 0, 0), _camera.up); + this.targetQuaternion.setFromRotationMatrix(this.m); + this.radius = _camera.position.distanceTo(focusPoint); + this.targetPosition.multiplyScalar(this.radius).add(focusPoint); + this.dummy.position.copy(focusPoint); + this.dummy.lookAt(_camera.position); + this.q1.copy(this.dummy.quaternion); + this.dummy.lookAt(this.targetPosition); + this.q2.copy(this.dummy.quaternion); + // clock + this.clock = new THREE.Clock(); + this._animate(resolve); + }); + + // tween + // if (this.tween) this.tween.stop(); + // var preelapsed = 0; + // this.tween = new TWEEN.Tween({ time: 0 }) + // .to({ time: 1 }, 1500) + // .onUpdate((object, elapsed) => { + // var delta = elapsed - preelapsed; + // preelapsed = elapsed; + // if (delta === 0) return false; + // // const step = delta * this.turnRate; + // // animate position by doing a slerp and then scaling the position on the unit sphere + // this.update(delta); + // render(); + // }) + // .onComplete(() => { + // this.tween = null; + // }) + // .start(); + } + _animate(resolve: Function) { + const delta = this.clock.getDelta(); + const step = delta * this.turnRate; + const focusPoint = this.controls.target; + const _camera = this.renderView.camera; + // animate position by doing a slerp and then scaling the position on the unit sphere + this.q1.rotateTowards(this.q2, step); + _camera.position + .set(0, 0, 1) + .applyQuaternion(this.q1) + .multiplyScalar(this.radius) + .add(focusPoint); + // animate orientation + _camera.quaternion.rotateTowards(this.targetQuaternion, step); + this.renderView.render(); + if (this.q1.angleTo(this.q2) < 1e-6) { + resolve(true); + } else { + requestAnimationFrame(this._animate.bind(this, resolve)); + } + } +} diff --git a/frontend/text-tool/src/packages/pc-render/config/index.ts b/frontend/text-tool/src/packages/pc-render/config/index.ts new file mode 100644 index 00000000..e959d589 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/config/index.ts @@ -0,0 +1,17 @@ +export const Event = { + SELECT: 'select', + OBJECT_DBLCLICK: 'object_dblclick', + ADD_OBJECT: 'add_object', + REMOVE_OBJECT: 'remove_object', + VISIBLE_CHANGE: 'visible_change', + USER_DATA_CHANGE: 'user_data_change', + OBJECT_TRANSFORM: 'object_transform', + OBJECT_2D_TRANSFORM: 'object_2d_transform', + RENDER_BEFORE: 'render_before', + RENDER_AFTER: 'render_after', + CONTAINER_TRANSFORM: 'container_transform', + LOAD_POINT_AFTER: 'load_point_after', + LOAD_POINT_BEFORE: 'load_point_before', + POINTS_CHANGE: 'POINTS_CHANGE', + CLEAR_DATA: 'clear_data', +}; diff --git a/frontend/text-tool/src/packages/pc-render/index.ts b/frontend/text-tool/src/packages/pc-render/index.ts new file mode 100644 index 00000000..3530d4bc --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/index.ts @@ -0,0 +1,63 @@ +// import Action from './action/Action'; +import OrbitControlsAction from './action/OrbitControlsAction'; +import TransformControlsAction from './action/TransformControlsAction'; +import SelectAction from './action/SelectAction'; +import ResizeTransAction from './action/ResizeTransAction'; +import CreateAction from './action/CreateAction'; +import LabelAction from './action/LabelAction'; +import Render2DAction from './action/Render2DAction'; +import Edit2DAction from './action/Edit2DAction'; +import Transform2DAction from './action/Transform2DAction'; +import ViewHelperAction from './action/ViewHelperAction'; + +import { registryAction } from './action/index'; + +[ + OrbitControlsAction, + TransformControlsAction, + SelectAction, + ResizeTransAction, + CreateAction, + LabelAction, + Render2DAction, + Edit2DAction, + Transform2DAction, + ViewHelperAction, +].forEach((action) => { + registryAction(action.actionName, action as any); +}); + +export { Event } from './config/index'; + +export * from './points'; +export { default as PointCloud } from './PointCloud'; +export { default as RenderView } from './renderView/Render'; +export { default as MainRenderView } from './renderView/MainRenderView'; +export { default as SideRenderView } from './renderView/SideRenderView'; +export { default as Image2DRenderView } from './renderView/Image2DRenderView'; +export { default as Image2DRenderProxy } from './renderView/Image2DRenderProxy'; + +export { default as PointsMaterial } from './material/PointsMaterial'; +export { default as PCDLoader } from './loader/PCDLoader'; +export * as utils from '../pc-render/utils'; +export { + OrbitControlsAction, + TransformControlsAction, + SelectAction, + ResizeTransAction, + CreateAction, + LabelAction, + Render2DAction, + Edit2DAction, + Transform2DAction, + ViewHelperAction, +}; + +export * from './objects'; + +// type +export type { axisType } from './renderView/SideRenderView'; +export type { viewType } from './common/ViewHelper'; +export type { wayToFocus } from './renderView/MainRenderView'; +export type { IColorRangeItem, IUniformOption } from './material/PointsMaterial'; +export * from './type'; diff --git a/frontend/text-tool/src/packages/pc-render/loader/PCDLoader.ts b/frontend/text-tool/src/packages/pc-render/loader/PCDLoader.ts new file mode 100644 index 00000000..392f53b0 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/loader/PCDLoader.ts @@ -0,0 +1,354 @@ +import { FileLoader, Loader, LoaderUtils } from 'three'; + +type ICallBack = (args?: any) => void; +type PCDData = 'ascii' | 'binary_compressed' | 'binary'; +type PCDFields = 'x' | 'y' | 'z' | 'i' | 'intensity' | 'rgb' | 'normal_x' | 'normal_y' | 'normal_z'; +interface IPCDHeader { + data: PCDData; + offset: { [key in PCDFields]: number }; + headerLen: number; + height: number; + points: number; + size: number[]; + rowSize: number; +} + +class PCDLoader extends Loader { + littleEndian: boolean; + constructor() { + super(); + + this.littleEndian = true; + } + + load(url: string, onLoad: ICallBack, onProgress?: ICallBack, onError?: ICallBack) { + const scope = this; + + const loader = new FileLoader(scope.manager); + loader.setPath(scope.path); + loader.setResponseType('arraybuffer'); + loader.setRequestHeader(scope.requestHeader); + loader.setWithCredentials(scope.withCredentials); + loader.load( + url, + function (data) { + try { + onLoad(scope.parse(data, url)); + } catch (e) { + if (onError) { + onError(e); + } else { + console.error(e); + } + + scope.manager.itemError(url); + } + }, + onProgress, + onError, + ); + } + + parse(data: any, url: string) { + function parseHeader(data: string) { + const PCDheader = {} as any; + const result1 = data.search(/[\r\n]DATA\s(\S*)\s/i); + const result2 = /[\r\n]DATA\s(\S*)\s/i.exec(data.substr(result1 - 1)) as any; + + PCDheader.data = result2[1]; + PCDheader.headerLen = result2[0].length + result1; + PCDheader.str = data.substr(0, PCDheader.headerLen); + + // remove comments + + PCDheader.str = PCDheader.str.replace(/\#.*/gi, ''); + + // parse + + PCDheader.version = /VERSION (.*)/i.exec(PCDheader.str); + PCDheader.fields = /FIELDS (.*)/i.exec(PCDheader.str); + PCDheader.size = /SIZE (.*)/i.exec(PCDheader.str); + PCDheader.type = /TYPE (.*)/i.exec(PCDheader.str); + PCDheader.count = /COUNT (.*)/i.exec(PCDheader.str); + PCDheader.width = /WIDTH (.*)/i.exec(PCDheader.str); + PCDheader.height = /HEIGHT (.*)/i.exec(PCDheader.str); + PCDheader.viewpoint = /VIEWPOINT (.*)/i.exec(PCDheader.str); + PCDheader.points = /POINTS (.*)/i.exec(PCDheader.str); + + // evaluate + + if (PCDheader.version !== null) PCDheader.version = parseFloat(PCDheader.version[1]); + + if (PCDheader.fields !== null) PCDheader.fields = PCDheader.fields[1].split(' '); + + if (PCDheader.type !== null) PCDheader.type = PCDheader.type[1].split(' '); + + if (PCDheader.width !== null) PCDheader.width = parseInt(PCDheader.width[1]); + + if (PCDheader.height !== null) PCDheader.height = parseInt(PCDheader.height[1]); + + if (PCDheader.viewpoint !== null) PCDheader.viewpoint = PCDheader.viewpoint[1]; + + if (PCDheader.points !== null) PCDheader.points = parseInt(PCDheader.points[1], 10); + + if (PCDheader.points === null) PCDheader.points = PCDheader.width * PCDheader.height; + + if (PCDheader.size !== null) { + PCDheader.size = PCDheader.size[1].split(' ').map(function (x: any) { + return parseInt(x, 10); + }); + } + + if (PCDheader.count !== null) { + PCDheader.count = PCDheader.count[1].split(' ').map(function (x: any) { + return parseInt(x, 10); + }); + } else { + PCDheader.count = []; + + for (let i = 0, l = PCDheader.fields.length; i < l; i++) { + PCDheader.count.push(1); + } + } + + PCDheader.offset = {}; + + let sizeSum = 0; + + for (let i = 0, l = PCDheader.fields.length; i < l; i++) { + if (PCDheader.data === 'ascii') { + PCDheader.offset[PCDheader.fields[i]] = i; + } else { + PCDheader.offset[PCDheader.fields[i]] = sizeSum; + sizeSum += PCDheader.size[i] * PCDheader.count[i]; + } + } + + // for binary only + + PCDheader.rowSize = sizeSum; + + return PCDheader; + } + + const textData = LoaderUtils.decodeText(new Uint8Array(data)); + + // parse header (always ascii format) + + function decompressLZF(inData: Uint8Array, outLength: number) { + const inLength = inData.length; + const outData = new Uint8Array(outLength); + let inPtr = 0; + let outPtr = 0; + let ctrl; + let len; + let ref; + do { + ctrl = inData[inPtr++]; + if (ctrl < 1 << 5) { + ctrl++; + if (outPtr + ctrl > outLength) + throw new Error('Output buffer is not large enough'); + if (inPtr + ctrl > inLength) throw new Error('Invalid compressed data'); + do { + outData[outPtr++] = inData[inPtr++]; + } while (--ctrl); + } else { + len = ctrl >> 5; + ref = outPtr - ((ctrl & 0x1f) << 8) - 1; + if (inPtr >= inLength) throw new Error('Invalid compressed data'); + if (len === 7) { + len += inData[inPtr++]; + if (inPtr >= inLength) throw new Error('Invalid compressed data'); + } + + ref -= inData[inPtr++]; + if (outPtr + len + 2 > outLength) + throw new Error('Output buffer is not large enough'); + if (ref < 0) throw new Error('Invalid compressed data'); + if (ref >= outPtr) throw new Error('Invalid compressed data'); + do { + outData[outPtr++] = outData[ref++]; + } while (--len + 2); + } + } while (inPtr < inLength); + + return outData; + } + + const PCDheader = parseHeader(textData) as IPCDHeader; + // parse data + + const position = []; + // const normal = []; + const color = []; + const intensity = []; + + // ascii + + let N = 1; + if (PCDheader.data === 'ascii') { + const offset = PCDheader.offset; + const pcdData = textData.substr(PCDheader.headerLen); + const lines = pcdData.split('\n'); + + for (let i = 0, l = lines.length; i < l; i++) { + if (lines[i] === '') continue; + + const line = lines[i].split(' '); + // const line = lines[i].split(/\s|,/); + + if (offset.x !== undefined) { + // [...Array(N)].forEach((e) => { + position.push(parseFloat(line[offset.x])); + position.push(parseFloat(line[offset.y])); + position.push(parseFloat(line[offset.z])); + // }); + } + + if (offset.rgb !== undefined) { + const rgb = parseFloat(line[offset.rgb]); + const r = (rgb >> 16) & 0x0000ff; + const g = (rgb >> 8) & 0x0000ff; + const b = (rgb >> 0) & 0x0000ff; + + // [...Array(N)].forEach((e) => { + color.push(r / 255, g / 255, b / 255); + // }); + } + if (offset.i !== undefined) { + // [...Array(N)].forEach((e) => { + intensity.push(parseFloat(line[offset.i])); + // }); + } + if (offset.intensity !== undefined) { + // [...Array(N)].forEach((e) => { + intensity.push(parseFloat(line[offset.intensity])); + // }); + } + // if (offset.normal_x !== undefined) { + // [...Array(N)].forEach((e) => { + // normal.push(parseFloat(line[offset.normal_x])); + // normal.push(parseFloat(line[offset.normal_y])); + // normal.push(parseFloat(line[offset.normal_z])); + // }); + // } + } + } + + if (PCDheader.data === 'binary_compressed') { + const sizes = new Uint32Array(data.slice(PCDheader.headerLen, PCDheader.headerLen + 8)); + const compressedSize = sizes[0]; + const decompressedSize = sizes[1]; + const decompressed = decompressLZF( + new Uint8Array(data, PCDheader.headerLen + 8, compressedSize), + decompressedSize, + ); + const dataview = new DataView(decompressed.buffer); + + const offset = PCDheader.offset; + + for (let i = 0; i < PCDheader.points; i++) { + if (offset.x !== undefined) { + position.push( + dataview.getFloat32( + PCDheader.points * offset.x + PCDheader.size[0] * i, + this.littleEndian, + ), + ); + position.push( + dataview.getFloat32( + PCDheader.points * offset.y + PCDheader.size[1] * i, + this.littleEndian, + ), + ); + position.push( + dataview.getFloat32( + PCDheader.points * offset.z + PCDheader.size[2] * i, + this.littleEndian, + ), + ); + } + + if (offset.rgb !== undefined) { + color.push( + dataview.getUint8( + PCDheader.points * offset.rgb + PCDheader.size[3] * i + 0, + ) / 255.0, + ); + color.push( + dataview.getUint8( + PCDheader.points * offset.rgb + PCDheader.size[3] * i + 1, + ) / 255.0, + ); + color.push( + dataview.getUint8( + PCDheader.points * offset.rgb + PCDheader.size[3] * i + 2, + ) / 255.0, + ); + } + + // if (offset.normal_x !== undefined) { + // normal.push( + // dataview.getFloat32( + // PCDheader.points * offset.normal_x + PCDheader.size[4] * i, + // this.littleEndian, + // ), + // ); + // normal.push( + // dataview.getFloat32( + // PCDheader.points * offset.normal_y + PCDheader.size[5] * i, + // this.littleEndian, + // ), + // ); + // normal.push( + // dataview.getFloat32( + // PCDheader.points * offset.normal_z + PCDheader.size[6] * i, + // this.littleEndian, + // ), + // ); + // } + } + } + + if (PCDheader.data === 'binary') { + const dataview = new DataView(data, PCDheader.headerLen); + const offset = PCDheader.offset; + + for (let i = 0, row = 0; i < PCDheader.points; i++, row += PCDheader.rowSize) { + if (offset.x !== undefined) { + position.push(dataview.getFloat32(row + offset.x, this.littleEndian)); + position.push(dataview.getFloat32(row + offset.y, this.littleEndian)); + position.push(dataview.getFloat32(row + offset.z, this.littleEndian)); + } + + if (offset.i !== undefined) { + intensity.push(dataview.getFloat32(row + offset.i, this.littleEndian)); + } else if (offset.intensity !== undefined) { + intensity.push(dataview.getFloat32(row + offset.intensity, this.littleEndian)); + } + + if (offset.rgb !== undefined) { + color.push(dataview.getUint8(row + offset.rgb + 2) / 255.0); + color.push(dataview.getUint8(row + offset.rgb + 1) / 255.0); + color.push(dataview.getUint8(row + offset.rgb + 0) / 255.0); + } + + // if (offset.normal_x !== undefined) { + // normal.push(dataview.getFloat32(row + offset.normal_x, this.littleEndian)); + // normal.push(dataview.getFloat32(row + offset.normal_y, this.littleEndian)); + // normal.push(dataview.getFloat32(row + offset.normal_z, this.littleEndian)); + // } + } + } + + // build geometry + return { + position, + color, + intensity, + }; + } +} + +export default PCDLoader; diff --git a/frontend/text-tool/src/packages/pc-render/loader/bin.ts b/frontend/text-tool/src/packages/pc-render/loader/bin.ts new file mode 100644 index 00000000..3cd172fb --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/loader/bin.ts @@ -0,0 +1,443 @@ +'use strict'; + +function CustomView(buffer: ArrayBuffer) { + this.buffer = buffer; + this.u8 = new Uint8Array(buffer); + + var tmp = new ArrayBuffer(4); + var tmpf = new Float32Array(tmp); + var tmpu8 = new Uint8Array(tmp); + + this.getUint32 = function (i) { + return (this.u8[i + 3] << 24) | (this.u8[i + 2] << 16) | (this.u8[i + 1] << 8) | this.u8[i]; + }; + + this.getUint16 = function (i) { + return (this.u8[i + 1] << 8) | this.u8[i]; + }; + + this.getFloat32 = function (i) { + tmpu8[0] = this.u8[i + 0]; + tmpu8[1] = this.u8[i + 1]; + tmpu8[2] = this.u8[i + 2]; + tmpu8[3] = this.u8[i + 3]; + + return tmpf[0]; + }; + + this.getUint8 = function (i) { + return this.u8[i]; + }; +} + +onmessage = function (event) { + try { + var buffer = event.data.buffer; + var pointAttributes = event.data.pointAttributes; + var numPoints = buffer.byteLength / pointAttributes.byteSize; + var cv = new CustomView(buffer); + var version = new Version(event.data.version); + var nodeOffset = event.data.offset; + var scale = event.data.scale; + var spacing = event.data.spacing; + var hasChildren = event.data.hasChildren; + var name = event.data.name; + + var tightBoxMin = [ + Number.POSITIVE_INFINITY, + Number.POSITIVE_INFINITY, + Number.POSITIVE_INFINITY, + ]; + var tightBoxMax = [ + Number.NEGATIVE_INFINITY, + Number.NEGATIVE_INFINITY, + Number.NEGATIVE_INFINITY, + ]; + var mean = [0, 0, 0]; + + var attributeBuffers = {}; + var inOffset = 0; + + for (var pointAttribute of pointAttributes.attributes) { + if (pointAttribute.name === PointAttribute.POSITION_CARTESIAN.name) { + var buff = new ArrayBuffer(numPoints * 4 * 3); + var positions = new Float32Array(buff); + + for (var j = 0; j < numPoints; j++) { + var x, y, z; + + if (version.newerThan('1.3')) { + x = cv.getUint32(inOffset + j * pointAttributes.byteSize + 0, true) * scale; + y = cv.getUint32(inOffset + j * pointAttributes.byteSize + 4, true) * scale; + z = cv.getUint32(inOffset + j * pointAttributes.byteSize + 8, true) * scale; + } else { + x = cv.getFloat32(j * pointAttributes.byteSize + 0, true) + nodeOffset[0]; + y = cv.getFloat32(j * pointAttributes.byteSize + 4, true) + nodeOffset[1]; + z = cv.getFloat32(j * pointAttributes.byteSize + 8, true) + nodeOffset[2]; + } + + positions[3 * j + 0] = x; + positions[3 * j + 1] = y; + positions[3 * j + 2] = z; + + mean[0] += x / numPoints; + mean[1] += y / numPoints; + mean[2] += z / numPoints; + + tightBoxMin[0] = Math.min(tightBoxMin[0], x); + tightBoxMin[1] = Math.min(tightBoxMin[1], y); + tightBoxMin[2] = Math.min(tightBoxMin[2], z); + + tightBoxMax[0] = Math.max(tightBoxMax[0], x); + tightBoxMax[1] = Math.max(tightBoxMax[1], y); + tightBoxMax[2] = Math.max(tightBoxMax[2], z); + } + + attributeBuffers[pointAttribute.name] = { buffer: buff, attribute: pointAttribute }; + } else if (pointAttribute.name === PointAttribute.COLOR_PACKED.name) { + var buff = new ArrayBuffer(numPoints * 4); + var colors = new Uint8Array(buff); + + for (var j = 0; j < numPoints; j++) { + colors[4 * j + 0] = cv.getUint8(inOffset + j * pointAttributes.byteSize + 0); + colors[4 * j + 1] = cv.getUint8(inOffset + j * pointAttributes.byteSize + 1); + colors[4 * j + 2] = cv.getUint8(inOffset + j * pointAttributes.byteSize + 2); + } + + attributeBuffers[pointAttribute.name] = { buffer: buff, attribute: pointAttribute }; + } else if (pointAttribute.name === PointAttribute.INTENSITY.name) { + var buff = new ArrayBuffer(numPoints * 4); + var intensities = new Float32Array(buff); + + for (var j = 0; j < numPoints; j++) { + var intensity = cv.getUint16(inOffset + j * pointAttributes.byteSize, true); + intensities[j] = intensity; + } + + attributeBuffers[pointAttribute.name] = { buffer: buff, attribute: pointAttribute }; + } else if (pointAttribute.name === PointAttribute.CLASSIFICATION.name) { + var buff = new ArrayBuffer(numPoints); + var classifications = new Uint8Array(buff); + + for (var j = 0; j < numPoints; j++) { + var classification = cv.getUint8(inOffset + j * pointAttributes.byteSize); + classifications[j] = classification; + } + + attributeBuffers[pointAttribute.name] = { buffer: buff, attribute: pointAttribute }; + } else if (pointAttribute.name === PointAttribute.NORMAL_SPHEREMAPPED.name) { + var buff = new ArrayBuffer(numPoints * 4 * 3); + var normals = new Float32Array(buff); + + for (var j = 0; j < numPoints; j++) { + var bx = cv.getUint8(inOffset + j * pointAttributes.byteSize + 0); + var by = cv.getUint8(inOffset + j * pointAttributes.byteSize + 1); + + var ex = bx / 255; + var ey = by / 255; + + var nx = ex * 2 - 1; + var ny = ey * 2 - 1; + var nz = 1; + var nw = -1; + + var l = nx * -nx + ny * -ny + nz * -nw; + nz = l; + nx = nx * Math.sqrt(l); + ny = ny * Math.sqrt(l); + + nx = nx * 2; + ny = ny * 2; + nz = nz * 2 - 1; + + normals[3 * j + 0] = nx; + normals[3 * j + 1] = ny; + normals[3 * j + 2] = nz; + } + + attributeBuffers[pointAttribute.name] = { buffer: buff, attribute: pointAttribute }; + } else if (pointAttribute.name === PointAttribute.NORMAL_OCT16.name) { + var buff = new ArrayBuffer(numPoints * 4 * 3); + var normals = new Float32Array(buff); + + for (var j = 0; j < numPoints; j++) { + var bx = cv.getUint8(inOffset + j * pointAttributes.byteSize + 0); + var by = cv.getUint8(inOffset + j * pointAttributes.byteSize + 1); + + var u = (bx / 255) * 2 - 1; + var v = (by / 255) * 2 - 1; + + var z = 1 - Math.abs(u) - Math.abs(v); + + var x = 0; + var y = 0; + if (z >= 0) { + x = u; + y = v; + } else { + x = -(v / Math.sign(v) - 1) / Math.sign(u); + y = -(u / Math.sign(u) - 1) / Math.sign(v); + } + + var length = Math.sqrt(x * x + y * y + z * z); + x = x / length; + y = y / length; + z = z / length; + + normals[3 * j + 0] = x; + normals[3 * j + 1] = y; + normals[3 * j + 2] = z; + } + + attributeBuffers[pointAttribute.name] = { buffer: buff, attribute: pointAttribute }; + } else if (pointAttribute.name === PointAttribute.NORMAL.name) { + var buff = new ArrayBuffer(numPoints * 4 * 3); + var normals = new Float32Array(buff); + + for (var j = 0; j < numPoints; j++) { + var x = cv.getFloat32(inOffset + j * pointAttributes.byteSize + 0, true); + var y = cv.getFloat32(inOffset + j * pointAttributes.byteSize + 4, true); + var z = cv.getFloat32(inOffset + j * pointAttributes.byteSize + 8, true); + + normals[3 * j + 0] = x; + normals[3 * j + 1] = y; + normals[3 * j + 2] = z; + } + + attributeBuffers[pointAttribute.name] = { buffer: buff, attribute: pointAttribute }; + } + + inOffset += pointAttribute.byteSize; + } + + //add indices + var buff = new ArrayBuffer(numPoints * 4); + var indices = new Uint32Array(buff); + + for (var i = 0; i < numPoints; i++) { + indices[i] = i; + } + + attributeBuffers[PointAttribute.INDICES.name] = { + buffer: buff, + attribute: PointAttribute.INDICES, + }; + + var message = { + buffer: buffer, + mean: mean, + attributeBuffers: attributeBuffers, + tightBoundingBox: { min: tightBoxMin, max: tightBoxMax }, + }; + + var transferables = []; + for (var property in message.attributeBuffers) { + transferables.push(message.attributeBuffers[property].buffer); + } + transferables.push(buffer); + + postMessage(message, transferables); + } catch (e) { + postMessage({ error: 'Exeption thrown during execution.' }); + } +}; + +function Version(version) { + this.version = version; + var vmLength = version.indexOf('.') === -1 ? version.length : version.indexOf('.'); + this.versionMajor = parseInt(version.substr(0, vmLength)); + this.versionMinor = parseInt(version.substr(vmLength + 1)); + + if (this.versionMinor.length === 0) { + this.versionMinor = 0; + } +} + +Version.prototype.newerThan = function (version) { + var v = new Version(version); + + if ( + this.versionMajor > v.versionMajor || + (this.versionMajor === v.versionMajor && this.versionMinor > v.versionMinor) + ) { + return true; + } + + return false; +}; + +var PointAttributeNames = { + POSITION_CARTESIAN: 0, //float x, y, z, + COLOR_PACKED: 1, //byte r, g, b, a, I: [0,1] + COLOR_FLOATS_1: 2, //float r, g, b, I: [0,1] + COLOR_FLOATS_255: 3, //float r, g, b, I: [0,255] + NORMAL_FLOATS: 4, //float x, y, z, + FILLER: 5, + INTENSITY: 6, + CLASSIFICATION: 7, + NORMAL_SPHEREMAPPED: 8, + NORMAL_OCT16: 9, + NORMAL: 10, + RETURN_NUMBER: 11, + NUMBER_OF_RETURNS: 12, + SOURCE_ID: 13, + INDICES: 14, + SPACING: 15, +}; + +/** + * Some types of possible point attribute data formats + * + * @class + */ +var PointAttributeTypes = { + DATA_TYPE_DOUBLE: { ordinal: 0, size: 8 }, + DATA_TYPE_FLOAT: { ordinal: 1, size: 4 }, + DATA_TYPE_INT8: { ordinal: 2, size: 1 }, + DATA_TYPE_UINT8: { ordinal: 3, size: 1 }, + DATA_TYPE_INT16: { ordinal: 4, size: 2 }, + DATA_TYPE_UINT16: { ordinal: 5, size: 2 }, + DATA_TYPE_INT32: { ordinal: 6, size: 4 }, + DATA_TYPE_UINT32: { ordinal: 7, size: 4 }, + DATA_TYPE_INT64: { ordinal: 8, size: 8 }, + DATA_TYPE_UINT64: { ordinal: 9, size: 8 }, +}; + +var i = 0; +for (var obj in PointAttributeTypes) { + PointAttributeTypes[i] = PointAttributeTypes[obj]; + i++; +} + +function PointAttribute(name, type, numElements) { + this.name = name; + this.type = type; + this.numElements = numElements; + this.byteSize = this.numElements * this.type.size; +} + +PointAttribute.POSITION_CARTESIAN = new PointAttribute( + PointAttributeNames.POSITION_CARTESIAN, + PointAttributeTypes.DATA_TYPE_FLOAT, + 3, +); +PointAttribute.RGBA_PACKED = new PointAttribute( + PointAttributeNames.COLOR_PACKED, + PointAttributeTypes.DATA_TYPE_INT8, + 4, +); +PointAttribute.COLOR_PACKED = PointAttribute.RGBA_PACKED; +PointAttribute.RGB_PACKED = new PointAttribute( + PointAttributeNames.COLOR_PACKED, + PointAttributeTypes.DATA_TYPE_INT8, + 3, +); +PointAttribute.NORMAL_FLOATS = new PointAttribute( + PointAttributeNames.NORMAL_FLOATS, + PointAttributeTypes.DATA_TYPE_FLOAT, + 3, +); +PointAttribute.FILLER_1B = new PointAttribute( + PointAttributeNames.FILLER, + PointAttributeTypes.DATA_TYPE_UINT8, + 1, +); +PointAttribute.INTENSITY = new PointAttribute( + PointAttributeNames.INTENSITY, + PointAttributeTypes.DATA_TYPE_UINT16, + 1, +); +PointAttribute.CLASSIFICATION = new PointAttribute( + PointAttributeNames.CLASSIFICATION, + PointAttributeTypes.DATA_TYPE_UINT8, + 1, +); +PointAttribute.NORMAL_SPHEREMAPPED = new PointAttribute( + PointAttributeNames.NORMAL_SPHEREMAPPED, + PointAttributeTypes.DATA_TYPE_UINT8, + 2, +); +PointAttribute.NORMAL_OCT16 = new PointAttribute( + PointAttributeNames.NORMAL_OCT16, + PointAttributeTypes.DATA_TYPE_UINT8, + 2, +); +PointAttribute.NORMAL = new PointAttribute( + PointAttributeNames.NORMAL, + PointAttributeTypes.DATA_TYPE_FLOAT, + 3, +); +PointAttribute.RETURN_NUMBER = new PointAttribute( + PointAttributeNames.RETURN_NUMBER, + PointAttributeTypes.DATA_TYPE_UINT8, + 1, +); +PointAttribute.NUMBER_OF_RETURNS = new PointAttribute( + PointAttributeNames.NUMBER_OF_RETURNS, + PointAttributeTypes.DATA_TYPE_UINT8, + 1, +); +PointAttribute.SOURCE_ID = new PointAttribute( + PointAttributeNames.SOURCE_ID, + PointAttributeTypes.DATA_TYPE_UINT8, + 1, +); +PointAttribute.INDICES = new PointAttribute( + PointAttributeNames.INDICES, + PointAttributeTypes.DATA_TYPE_UINT32, + 1, +); +PointAttribute.SPACING = new PointAttribute( + PointAttributeNames.SPACING, + PointAttributeTypes.DATA_TYPE_FLOAT, + 1, +); + +function PointAttributes(pointAttributes) { + this.attributes = []; + this.byteSize = 0; + this.size = 0; + + if (pointAttributes != null) { + for (var i = 0; i < pointAttributes.length; i++) { + var pointAttributeName = pointAttributes[i]; + var pointAttribute = PointAttribute[pointAttributeName]; + this.attributes.push(pointAttribute); + this.byteSize += pointAttribute.byteSize; + this.size++; + } + } +} + +PointAttributes.prototype.add = function (pointAttribute) { + this.attributes.push(pointAttribute); + this.byteSize += pointAttribute.byteSize; + this.size++; +}; + +PointAttributes.prototype.hasColors = function () { + for (var name in this.attributes) { + var pointAttribute = this.attributes[name]; + if (pointAttribute.name === PointAttributeNames.COLOR_PACKED) { + return true; + } + } + + return false; +}; + +PointAttributes.prototype.hasNormals = function () { + for (var name in this.attributes) { + var pointAttribute = this.attributes[name]; + if ( + pointAttribute === PointAttribute.NORMAL_SPHEREMAPPED || + pointAttribute === PointAttribute.NORMAL_FLOATS || + pointAttribute === PointAttribute.NORMAL || + pointAttribute === PointAttribute.NORMAL_OCT16 + ) { + return true; + } + } + + return false; +}; diff --git a/frontend/text-tool/src/packages/pc-render/loader/index.ts b/frontend/text-tool/src/packages/pc-render/loader/index.ts new file mode 100644 index 00000000..307868d3 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/loader/index.ts @@ -0,0 +1 @@ +export { default as PCDLoader } from './PCDLoader'; diff --git a/frontend/text-tool/src/packages/pc-render/material/PointsMaterial.ts b/frontend/text-tool/src/packages/pc-render/material/PointsMaterial.ts new file mode 100644 index 00000000..b96449e3 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/material/PointsMaterial.ts @@ -0,0 +1,295 @@ +import * as THREE from 'three'; + +interface IOption { + hasIntensity: boolean; +} + +function getShaderCode(option: IOption = { hasIntensity: false }) { + let defines = []; + if (option.hasIntensity) defines.push('#define FLAG_INTENSITY'); + + let vertex = ` + ${defines.join('\n')} + precision mediump float; + precision mediump int; + + struct ColorItem { + float min; + float max; + vec3 color; + }; + + struct FilterBox { + // 0 color, 1 color filter, + float type; + vec3 min; + vec3 max; + vec3 color; + mat4 matrix; + }; + + + // color + uniform ColorItem colorRange[6]; + uniform float colorMode; + + // intensity + #ifdef FLAG_INTENSITY + uniform vec2 intensityRange; + attribute float intensity; + #endif + + + // matrix + uniform mat4 modelViewMatrix; + uniform mat4 projectionMatrix; + + // + uniform vec2 heightRange; + uniform vec2 pointHeight; + uniform float pointSize; + // 1.0 range-only, 2.0 range-opacity + uniform float trimType; + + // filter box + uniform float hasFilterBox; + uniform FilterBox boxInfo; + + // camera region + uniform float hasCameraRegion; + uniform mat4 regionMatrix; + + attribute vec3 position; + //attribute vec4 color; + + varying vec3 vColor; + varying float vOpacity; + + bool isInBox(vec3 pos, vec3 min, vec3 max){ + return pos.x >= min.x && pos.x <= max.x && pos.y >= min.y && pos.y <= max.y && pos.z >= min.z && pos.z <= max.z; + } + + vec3 getColor(float value){ + + vec3 color = vec3(1.0,0,0); + ColorItem item; + for(int i = 0; i < 6; i++){ + item = colorRange[i]; + if(value >= item.min && value < item.max) { + color = item.color; + break; + } + } + + return color; + } + + + void main() { + + vOpacity = 1.0; + float vDiscard = -1.0; + + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + gl_PointSize = pointSize; + if(position.z>heightRange.y||position.z 0.0 ) { + + #ifdef FLAG_INTENSITY + if(intensityRange.x > intensity || intensityRange.y < intensity){ + + vDiscard = 1.0; + + }else { + + // vColor = vec3(intensity/50.0); + vColor = getColor(position.z) * intensity / 10.0; + + } + #endif + + + } else { + vColor = getColor(position.z); + } + + + if(hasCameraRegion > 0.0){ + vec4 posNor = regionMatrix * vec4( position, 1.0 ); + posNor.xyz = posNor.xyz/posNor.w; + if( isInBox(posNor.xyz, vec3(-1.0,-1.0,-1.0),vec3(1.0,1.0,1.0))){ + vColor = vec3(1.0,1.0,1.0); + } + + } + + if(hasFilterBox > 0.0){ + vec4 boxPos = boxInfo.matrix * vec4( position, 1.0 ); + //type: 0 color, 1 color filter, + float type = boxInfo.type; + if(type == 0.0){ + if(isInBox(boxPos.xyz,boxInfo.min,boxInfo.max)){ + vColor = boxInfo.color; + } + }else if(type == 1.0){ + if(isInBox(boxPos.xyz,boxInfo.min,boxInfo.max)){ + vColor = boxInfo.color; + }else{ + // gl_Position = vec4(2.0); + vDiscard = 1.0; + } + } + + } + + if(vDiscard > 0.0) { + gl_Position = vec4(2.0,2.0,2.0,1.0); + vOpacity = 0.0; + } + + }`; + + let frag = ` + precision mediump float; + precision mediump int; + + varying vec3 vColor; + varying float vOpacity; + + void main() { + gl_FragColor = vec4(vColor*vOpacity,vOpacity); + + }`; + + return { vertex, frag }; +} + +export interface IColorRangeItem { + min: number; + max: number; + color: THREE.Color; +} + +// struct CameraRegion{ +// uniform mat4 matrix; +// uniform vec2 imgSize; +// } + +type IBoxInfoType = 0 | 1; + +export enum IColorMode { + COLOR_BY_HEIGHT = -1.0, + COLOR_BY_INTENSITY = 1.0, +} +export interface IUniformOption { + groundColor?: THREE.Color; + pointSize?: number; + // 1.0 range-only, 2.0 range-opacity + trimType?: 1 | 2; + // filter + hasFilterBox?: -1 | 1; + boxInfo?: { + type?: IBoxInfoType; + min?: THREE.Vector3; + max?: THREE.Vector3; + color?: THREE.Color; + matrix?: THREE.Matrix4; + }; + heightRange?: THREE.Vector2; + pointHeight?: THREE.Vector2; + colorMode?: IColorMode; + // intensity + intensityRange?: THREE.Vector2; + // color + colorN?: number; + colorRange?: IColorRangeItem[]; + // Camera Region + hasCameraRegion?: number; + regionMatrix?: THREE.Matrix4; +} + +type UniformKey = keyof IUniformOption; + +export default class PointsMaterial extends THREE.RawShaderMaterial { + option: IOption = { hasIntensity: false }; + constructor() { + super({ + uniforms: { + heightRange: { + value: new THREE.Vector2(-10000, 10000), + }, + pointHeight: { + value: new THREE.Vector2(-10000, 10000), + }, + pointSize: { value: 1 }, + trimType: { value: 1.0 }, + colorMode: { value: -1 }, + + intensityRange: { value: new THREE.Vector2(0, 255) }, + // filter + hasFilterBox: { value: -1 }, + boxInfo: { + value: { + // 0 color, 1 color filter, + type: 0, + min: new THREE.Vector3(-10, -10, -5), + max: new THREE.Vector3(10, 10, 5), + color: new THREE.Color(1, 1, 0), + matrix: new THREE.Matrix4(), + }, + }, + colorRange: { + value: [ + { min: -3, max: -1.5, color: new THREE.Color(1, 0, 0) }, + { min: -1.5, max: 0, color: new THREE.Color(0, 1, 0) }, + { min: 0, max: 2, color: new THREE.Color(0, 0, 1) }, + { min: 0, max: 2, color: new THREE.Color(0, 0, 1) }, + { min: 0, max: 2, color: new THREE.Color(0, 0, 1) }, + ], + }, + hasCameraRegion: { value: -1 }, + regionMatrix: { value: new THREE.Matrix4() }, + }, + vertexShader: '', + fragmentShader: '', + }); + this.updateShader(); + } + + updateShader() { + let { vertex, frag } = getShaderCode(this.option); + this.vertexShader = vertex; + this.fragmentShader = frag; + this.needsUpdate = true; + } + + setOption(option: IOption) { + Object.assign(this.option, option); + this.updateShader(); + } + + getUniforms(key: UniformKey) { + return this.uniforms[key].value; + } + setUniforms(option: IUniformOption) { + let keys = Object.keys(option) as UniformKey[]; + let uniforms = this.uniforms; + keys.forEach((key) => { + switch (key) { + case 'boxInfo': + Object.assign(uniforms[key].value, option[key]); + break; + case 'colorRange': + option.colorRange?.forEach((e, index) => { + uniforms.colorRange.value[index] = e; + }); + break; + default: + uniforms[key].value = option[key]; + } + }); + } +} diff --git a/frontend/text-tool/src/packages/pc-render/objects/Box.ts b/frontend/text-tool/src/packages/pc-render/objects/Box.ts new file mode 100644 index 00000000..ce5fb4b8 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/objects/Box.ts @@ -0,0 +1,164 @@ +import * as THREE from 'three'; +import { Intersect } from '../type'; +import { get } from '../utils/tempVar'; +import { AnnotateType } from '../type'; + +let defaultMaterial = new THREE.LineBasicMaterial({ + color: 0xffffff, + toneMapped: false, +}); + +let dashedMaterial = new THREE.LineDashedMaterial({ + color: 0xffffff, + linewidth: 1, + scale: 1, + dashSize: 0.1, + gapSize: 0.1, +}); +// defaultMaterial.depthTest = false; + +let { indices, positions, lineDistance } = getBoxInfo(); +const emptyGeometry = new THREE.BufferGeometry(); +emptyGeometry.setAttribute('position', new THREE.Float32BufferAttribute([], 3)); + +const defaultGeometry = new THREE.BufferGeometry(); +defaultGeometry.setIndex(new THREE.Uint16BufferAttribute(indices, 1)); +defaultGeometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3)); +defaultGeometry.setAttribute('lineDistance', new THREE.Float32BufferAttribute(lineDistance, 1)); +defaultGeometry.boundingSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0), 0.8660254037844386); +defaultGeometry.boundingBox = new THREE.Box3( + new THREE.Vector3(-0.5, -0.5, -0.5), + new THREE.Vector3(0.5, 0.5, 0.5), +); + +interface IEditConfig { + resize: boolean; +} + +export default class Box extends THREE.LineSegments { + // object: THREE.Box3; + dashed: boolean = false; + dashedMaterial: THREE.LineDashedMaterial = dashedMaterial; + annotateType: AnnotateType; + editConfig: IEditConfig = { resize: true }; + color: THREE.Color; + constructor() { + super(defaultGeometry, defaultMaterial); + + this.type = 'Box3Helper'; + // EMPTY + this.annotateType = AnnotateType.ANNOTATE_3D; + this.color = new THREE.Color(); + } + + raycast(raycaster: THREE.Raycaster, intersects: Intersect[]) { + if (!this.visible) return; + + let geometry = this.geometry; + const matrixWorld = this.matrixWorld; + + let _sphere = get(THREE.Sphere, 0).copy(geometry.boundingSphere as any); + _sphere.applyMatrix4(matrixWorld); + + if (raycaster.ray.intersectsSphere(_sphere) === false) return; + + let _inverseMatrix = get(THREE.Matrix4, 0).copy(matrixWorld).invert(); + let _ray = get(THREE.Ray, 0).copy(raycaster.ray).applyMatrix4(_inverseMatrix); + + if (geometry.boundingBox === null) geometry.computeBoundingBox(); + + if (_ray.intersectsBox(geometry.boundingBox as any) === false) return; + else { + let pos = get(THREE.Vector3, 0).set(0, 0, 0).applyMatrix4(matrixWorld); + let distance = pos.distanceTo(raycaster.ray.origin); + intersects.push({ object: this, distance, point: pos }); + } + } +} + +function getBoxInfo() { + const arrowLen = 0.4; + const arrowWidth = 0.15; + const dirStartX = 0.25; + const dirEndX = 1.0 + arrowLen; + const indices = [ + // box index +z + 0, 1, 1, 2, 2, 3, 3, 0, + // box index -z + 4, 5, 5, 6, 6, 7, 7, 4, + // box line + 0, 4, 1, 5, 2, 6, 3, 7, + // line + 8, 9, + // arrow + 9, 10, 10, 11, 11, 9, 9, 12, 13, 12, 13, 9, + ]; + + const lineDistance = [ + // box index +z + 0, 1, 2, 1, + // box index -z + 1, 2, 3, 2, + // line + 0, 2, + // arrow + 0.2, 0.2, 0.2, 0.2, + ]; + + const positions = [ + // box points +z + 1, + 1, + 1, + -1, + 1, + 1, + -1, + -1, + 1, + 1, + -1, + 1, + // box points -z + 1, + 1, + -1, + -1, + 1, + -1, + -1, + -1, + -1, + 1, + -1, + -1, + // line + dirStartX, + 0, + 0, + dirEndX, + 0, + 0, + // arrow pos 1 + 1.0, + arrowWidth, + 0, + // arrow pos 2 + 1.0, + -arrowWidth, + 0, + // arrow pos 3 + 1.0, + 0, + arrowWidth, + // arrow pos 4 + 1.0, + 0, + -arrowWidth, + ]; + positions.forEach((e, index) => { + positions[index] *= 0.5; + }); + + return { indices, positions, lineDistance }; +} diff --git a/frontend/text-tool/src/packages/pc-render/objects/index.ts b/frontend/text-tool/src/packages/pc-render/objects/index.ts new file mode 100644 index 00000000..5b336194 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/objects/index.ts @@ -0,0 +1,6 @@ +import Box from './Box'; +import { Object2D, Rect, Box2D } from './object2d'; + +export { Box, Object2D, Rect, Box2D }; +export type AnnotateObject = Box | Rect | Box2D | Object2D; +export type { Vector2Of4 } from './object2d'; diff --git a/frontend/text-tool/src/packages/pc-render/objects/object2d.ts b/frontend/text-tool/src/packages/pc-render/objects/object2d.ts new file mode 100644 index 00000000..795d327d --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/objects/object2d.ts @@ -0,0 +1,105 @@ +import * as THREE from 'three'; +import { AnnotateType } from '../type'; +import { isPointInRect } from '../utils'; + +export class Object2D { + connectId: number = -1; + uuid: string = ''; + viewId: string = ''; + visible: boolean = true; + dashed: boolean = false; + userData: any; + annotateType: AnnotateType; + position: any; + color: string; + constructor() { + this.userData = {}; + this.annotateType = AnnotateType.ANNOTATE_2D; + this.uuid = THREE.MathUtils.generateUUID(); + this.color = '#ffffff'; + } + isContainPosition(pos: THREE.Vector2) { + return false; + } +} + +let temp1 = new THREE.Vector2(); +let temp2 = new THREE.Vector2(); +// Rect +export class Rect extends Object2D { + center: THREE.Vector2 = new THREE.Vector2(); + size: THREE.Vector2 = new THREE.Vector2(); + constructor(center?: THREE.Vector2, size?: THREE.Vector2) { + super(); + if (center) this.center.copy(center); + if (size) this.size.copy(size); + } + + isContainPosition(pos: THREE.Vector2) { + temp1.copy(this.center).addScaledVector(this.size, -0.5); + temp2.copy(this.center).addScaledVector(this.size, 0.5); + + return pos.x >= temp1.x && pos.x <= temp2.x && pos.y >= temp1.y && pos.y <= temp2.y; + } +} + +// Box2D +export type Vector2Of4 = [THREE.Vector2, THREE.Vector2, THREE.Vector2, THREE.Vector2]; +let tempVector2Of4 = getVector2Of4(); +export class Box2D extends Object2D { + positions1: Vector2Of4 = getVector2Of4(); + positions2: Vector2Of4 = getVector2Of4(); + constructor(positions1?: Vector2Of4, positions2?: Vector2Of4) { + super(); + + if (positions1) this.copyVector2Of4(positions1, this.positions1); + if (positions2) this.copyVector2Of4(positions2, this.positions2); + } + isContainPosition(pos: THREE.Vector2) { + if (isPointInRect(pos, this.positions1)) return true; + if (isPointInRect(pos, this.positions2)) return true; + + for (let i = 0; i < 4; i++) { + let next = i + 1; + if (next === 4) next = 0; + tempVector2Of4[0] = this.positions1[i]; + tempVector2Of4[1] = this.positions1[next]; + tempVector2Of4[2] = this.positions2[next]; + tempVector2Of4[3] = this.positions2[i]; + if (isPointInRect(pos, tempVector2Of4)) return true; + } + + return false; + // temp1.copy(this.center).addScaledVector(this.size, -0.5); + // temp2.copy(this.center).addScaledVector(this.size, 0.5); + + // return pos.x >= temp1.x && pos.x <= temp2.x && pos.y >= temp1.y && pos.y <= temp2.y; + } + copyVector2Of4(source: Vector2Of4, target: Vector2Of4) { + target.forEach((e, index) => { + e.copy(source[index]); + }); + } + + getCenter(pos?: THREE.Vector2) { + pos = pos || new THREE.Vector2(); + + let sumX = 0; + let sumY = 0; + this.positions1.forEach((e, index) => { + sumX += this.positions1[index].x; + sumX += this.positions2[index].x; + + sumY += this.positions1[index].y; + sumY += this.positions2[index].y; + }); + + pos.set(sumX / 8, sumY / 8); + + return pos; + } +} + +function getVector2Of4(): Vector2Of4 { + return [new THREE.Vector2(), new THREE.Vector2(), new THREE.Vector2(), new THREE.Vector2()]; +} diff --git a/frontend/text-tool/src/packages/pc-render/points/Points.ts b/frontend/text-tool/src/packages/pc-render/points/Points.ts new file mode 100644 index 00000000..64e32a4e --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/points/Points.ts @@ -0,0 +1,116 @@ +import * as THREE from 'three'; +import { PCDLoader } from '../loader'; +import PointsMaterial from '../material/PointsMaterial'; +import { Event } from '../config'; +import { IPoints } from './type'; + +interface IData { + position?: number[]; + intensity?: number[]; + color?: number[]; +} + +enum Status { + loading = 'loading', + completed = 'completed', + failed = 'failed', +} + +function createGeometry(data: IData = { position: [], color: [], intensity: [] }) { + let geometry = new THREE.BufferGeometry(); + + let positionAttr = new THREE.Float32BufferAttribute(data.position || [], 3); + // positionAttr.usage = THREE.DynamicDrawUsage; + + let intensityAttr = new THREE.Float32BufferAttribute(data.intensity || [], 1); + // intensityAttr.usage = THREE.DynamicDrawUsage; + + let colorAttr = new THREE.Float32BufferAttribute(data.color || [], 3); + // colorAttr.usage = THREE.DynamicDrawUsage; + + geometry.setAttribute('position', positionAttr); + geometry.setAttribute('intensity', intensityAttr); + geometry.setAttribute('color', colorAttr); + return geometry; +} + +export default class Points extends THREE.Points implements IPoints { + completed: boolean = false; + loading: boolean = false; + timeStamp: number = 0; + loader: PCDLoader = new PCDLoader(); + constructor(material: PointsMaterial) { + let geometry = createGeometry(); + super(geometry, material); + } + + clear() { + return this; + } + + setBufferAttribute(attr: THREE.Float32BufferAttribute, data: number[] | Float32Array = []) { + if (!(data instanceof Float32Array)) data = new Float32Array(data); + attr.array = data; + attr.count = data.length / attr.itemSize; + attr.needsUpdate = true; + } + + updateData(data: IData) { + let geometry = this.geometry as THREE.BufferGeometry; + // let position = data.position as number[]; + // let oldPosition = geometry.getAttribute('position') as THREE.Float32BufferAttribute; + + geometry.dispose(); + this.geometry = createGeometry(data); + + // if (oldPosition.count < position.length / oldPosition.itemSize) { + // geometry.dispose(); + // geometry = createGeometry(data); + // this.geometry = geometry; + // } else { + // let positionAttr = geometry.getAttribute('position') as THREE.Float32BufferAttribute; + // this.setBufferAttribute(positionAttr, data.position || []); + // positionAttr.setDrawRange(0,); + + // let intensityAttr = geometry.getAttribute('intensity') as THREE.Float32BufferAttribute; + // this.setBufferAttribute(intensityAttr, data.intensity || []); + + // let colorAttr = geometry.getAttribute('color') as THREE.Float32BufferAttribute; + // this.setBufferAttribute(colorAttr, data.color || []); + // } + + this.geometry.computeBoundingSphere(); + this.dispatchEvent({ type: Event.POINTS_CHANGE }); + } + + loadUrl(url: string, onProgress?: (percent: number) => void): Promise { + this.loading = true; + let timeStamp = new Date().getTime(); + this.timeStamp = timeStamp; + + return new Promise((resolve, reject) => { + this.loader.load( + url, + (data: any) => { + if (timeStamp !== this.timeStamp) { + reject(); + return; + } + + // this.dispatchEvent({ type: Event.LOAD_POINT_BEFORE }); + + this.updateData(data); + this.loading = false; + resolve(this); + // this.dispatchEvent({ type: Event.LOAD_POINT_AFTER }); + }, + (e) => { + if (onProgress) onProgress(e.loaded / e.total); + }, + () => { + reject(); + }, + ); + }); + } +} diff --git a/frontend/text-tool/src/packages/pc-render/points/index.ts b/frontend/text-tool/src/packages/pc-render/points/index.ts new file mode 100644 index 00000000..45576775 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/points/index.ts @@ -0,0 +1 @@ +export { default as Points } from './Points'; diff --git a/frontend/text-tool/src/packages/pc-render/points/type.ts b/frontend/text-tool/src/packages/pc-render/points/type.ts new file mode 100644 index 00000000..1254ffce --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/points/type.ts @@ -0,0 +1,7 @@ +import type { Points } from 'three'; + +export interface IPoints extends Points { + loading: boolean; + updateData(data: any): void; + loadUrl(url: any): void; +} diff --git a/frontend/text-tool/src/packages/pc-render/renderView/Image2DRenderProxy.ts b/frontend/text-tool/src/packages/pc-render/renderView/Image2DRenderProxy.ts new file mode 100644 index 00000000..137debce --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/renderView/Image2DRenderProxy.ts @@ -0,0 +1,121 @@ +import * as THREE from 'three'; +import PointCloud from '../PointCloud'; +import Image2DRenderView from './Image2DRenderView'; +import Render from './Render'; + +export default class Image2DRenderProxy { + pointCloud: PointCloud; + views: Image2DRenderView[] = []; + // 2d + canvas: HTMLCanvasElement; + context: CanvasRenderingContext2D; + renderer: THREE.WebGLRenderer; + width = 100; + height = 100; + renderN = 0; + // + private renderTimer: number = 0; + clientRect: DOMRect = {} as DOMRect; + constructor(pointCloud: PointCloud) { + this.pointCloud = pointCloud; + // 2d + let canvas = document.createElement('canvas'); + canvas.className = 'render-2d-proxy'; + canvas.style.position = 'absolute'; + canvas.style.left = '0px'; + canvas.style.top = '0px'; + canvas.style.width = '100%'; + canvas.style.height = '100%'; + canvas.style.pointerEvents = 'none'; + canvas.width = this.width; + canvas.height = this.height; + let context = canvas.getContext('2d') as CanvasRenderingContext2D; + this.canvas = canvas; + this.context = context; + + // renderer + this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); + this.renderer.setPixelRatio(pointCloud.pixelRatio); + this.renderer.setSize(this.width, this.height); + this.renderer.sortObjects = false; + this.renderer.autoClear = false; + this.renderer.setClearColor(new THREE.Color(0, 0, 0), 0); + + Object.assign(this.renderer.domElement.style, { + position: 'absolute', + inset: '0px', + width: '100%', + height: '100%', + } as CSSStyleDeclaration); + } + + addView(view: Image2DRenderView) { + if (this.views.indexOf(view) >= 0) return; + this.views.push(view); + } + + removeView(view: Image2DRenderView) { + let index = this.views.findIndex((e) => e === view); + if (index >= 0) { + this.views.splice(index, 1); + } + } + + attach(container: HTMLElement) { + container.appendChild(this.canvas); + container.appendChild(this.renderer.domElement); + } + + needRender() { + return this.views.filter((e) => e.isEnable()).length > 0; + } + + updateSize() { + let width = this.canvas.clientWidth || 100; + let height = this.canvas.clientHeight || 100; + + if (width !== this.width || height !== this.height) { + this.width = width; + this.height = height; + + this.canvas.width = width; + this.canvas.height = height; + + this.renderer.domElement.width = width; + this.renderer.domElement.height = height; + } + } + + render() { + if (this.renderTimer) return; + this.renderTimer = requestAnimationFrame(this.renderFrame.bind(this)); + } + + renderFrame() { + let { context, renderer } = this; + this.renderTimer = 0; + + if (!this.needRender()) return; + + // console.log('proxy renderFrame'); + + this.updateSize(); + this.clientRect = this.canvas.getBoundingClientRect(); + // info 2d + // context.fillStyle = 'red'; + context.clearRect(0, 0, this.width, this.height); + + // info 3d + renderer.setScissorTest(false); + renderer.clear(); + renderer.setScissorTest(true); + + this.renderN = 0; + this.views.forEach((view) => { + context.save(); + view.renderFrame(); + context.restore(); + }); + // console.log('renderN', this.renderN); + } +} diff --git a/frontend/text-tool/src/packages/pc-render/renderView/Image2DRenderView.ts b/frontend/text-tool/src/packages/pc-render/renderView/Image2DRenderView.ts new file mode 100644 index 00000000..8c448d44 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/renderView/Image2DRenderView.ts @@ -0,0 +1,585 @@ +import * as THREE from 'three'; +import Render from './Render'; +import PointCloud from '../PointCloud'; +import PointsMaterial, { IUniformOption } from '../material/PointsMaterial'; +import * as _ from 'lodash'; +import { Object2D, Box, Rect, Vector2Of4, Box2D, AnnotateObject } from '../objects'; +import { IRenderViewConfig, ICameraInternal } from '../type'; +import { createMatrixFromCameraInternal, getMaxMinV2, reformProjectPoints } from '../utils'; +import { get } from '../utils/tempVar'; +import Image2DRenderProxy from './Image2DRenderProxy'; +import { Event } from '../config/'; + +const defaultActions: string[] = [ + 'select', + 'render-2d-shape', + 'create-obj', + 'edit-2d', + 'transform-2d', +]; +// const defaultActions: string[] = ['select-2d']; + +type ActionType = 'select' | 'render-2d-shape' | 'create-obj' | 'edit-2d' | 'transform-2d'; + +interface IOption { + cameraInternal: ICameraInternal; + cameraExternal: Array; + imgSize?: [number, number]; + imgUrl?: string; + imgObject: HTMLImageElement; +} + +let positionsFrontV3 = [...Array(4)].map((e) => new THREE.Vector3()); +let positionsBackV3 = [...Array(4)].map((e) => new THREE.Vector3()); + +let positionsFrontV2 = [...Array(4)].map((e) => new THREE.Vector2()); +let positionsBackV2 = [...Array(4)].map((e) => new THREE.Vector2()); + +let rotate180 = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 0, 1), Math.PI); + +export default class Image2DRenderView extends Render { + container: HTMLDivElement; + // matrix + // proxy offset matrix + proxyOffset: THREE.Vector2 = new THREE.Vector2(); + proxyTransformMatrix: THREE.Matrix4 = new THREE.Matrix4(); + // local matrix + containerMatrix: THREE.Matrix4 = new THREE.Matrix4(); + fitMatrix: THREE.Matrix4 = new THREE.Matrix4(); + transformMatrix: THREE.Matrix4 = new THREE.Matrix4(); + pointCloud: PointCloud; + width: number; + height: number; + // proxy + proxy: Image2DRenderProxy; + clientRect: DOMRect = {} as DOMRect; + // 2d + // canvas?: HTMLCanvasElement; + // context?: CanvasRenderingContext2D; + // 3d renderer + // renderer?: THREE.WebGLRenderer; + clipCamera: THREE.PerspectiveCamera; + camera: THREE.PerspectiveCamera; + cameraHelper: THREE.CameraHelper; + option: IOption = {} as IOption; + group: THREE.Group; + // project matrix + matrixExternal: THREE.Matrix4 = new THREE.Matrix4(); + matrixInternal: THREE.Matrix4 = new THREE.Matrix4(); + matrix: THREE.Matrix4 = new THREE.Matrix4(); + // img + img: HTMLImageElement | null = null; + imgSize: THREE.Vector2 = new THREE.Vector2(1, 1); + imgAspectRatio: number = 1; + // color + // selectColor: THREE.Color = new THREE.Color(1, 0, 0); + // selectColorCSS: string = '#FF0000'; + // highlightColor: THREE.Color = new THREE.Color(1, 0, 0); + // box filter matrix + boxInvertMatrix: THREE.Matrix4 = new THREE.Matrix4(); + // render flag + renderBox: boolean = true; + renderPoints: boolean = false; + renderRect: boolean = true; + renderBox2D: boolean = true; + // render config + lineWidth: number = 1; + + constructor( + container: HTMLDivElement, + pointCloud: PointCloud, + config: IRenderViewConfig & { proxy?: Image2DRenderProxy } = {}, + ) { + super(config.name || ''); + + this.container = container; + this.pointCloud = pointCloud; + + this.width = this.container.clientWidth || 10; + this.height = this.container.clientHeight || 10; + + let group = new THREE.Group(); + this.pointCloud.scene.add(group); + this.group = group; + + if (config.proxy) { + this.proxy = config.proxy; + } else { + this.proxy = new Image2DRenderProxy(pointCloud); + this.proxy.attach(this.container); + } + this.proxy.addView(this); + + this.camera = new THREE.PerspectiveCamera(50, this.width / this.height, 1, 1000); + // this.camera = new THREE.OrthographicCamera(-10, 10, 10, -10, 0.001, 1000); + this.group.add(this.camera); + + const helper = new THREE.CameraHelper(this.camera); + helper.visible = false; + this.group.add(helper); + // @ts-ignore + this.cameraHelper = helper; + + // clip + this.clipCamera = new THREE.PerspectiveCamera(100, this.width / this.height, 0.01, 100); + // let clipHelper = new THREE.CameraHelper(this.clipCamera); + // this.group.add(clipHelper); + + this.setActions(config.actions || defaultActions); + + // @ts-ignore + window.imgView = this; + } + + init(): void {} + + destroy(): void { + // this.renderer.dispose(); + // this.pointCloud.scene.remove(this.group); + // this.cameraHelper.dispose(); + } + + updateSize() { + let width = this.container.clientWidth || 100; + let height = this.container.clientHeight || 100; + + if (width !== this.width || height !== this.height) { + this.width = width; + this.height = height; + this.updateAspectRatioConfig(); + } + } + + updateAspectRatioConfig() { + let { imgSize, width, height, img } = this; + + if (!img) return; + + let scaleX = imgSize.x / width; + let scaleY = imgSize.y / height; + + let scale = 1; + let offsetX = 0; + let offsetY = 0; + if (scaleX > scaleY) { + scale = 1 / scaleX; + offsetY = (height - imgSize.y * scale) / 2; + } else { + scale = 1 / scaleY; + offsetX = (width - imgSize.x * scale) / 2; + } + this.fitMatrix.makeScale(scale, scale, 1); + + let translate = get(THREE.Matrix4); + translate.makeTranslation(offsetX, offsetY, 0); + this.fitMatrix.premultiply(translate); + } + + setOptions(option: IOption) { + this.option = option; + + let imgObject = option.imgObject; + + this.img = imgObject; + + this.imgSize.set(imgObject.naturalWidth, imgObject.naturalHeight); + this.imgAspectRatio = this.imgSize.x / this.imgSize.y; + this.updateAspectRatioConfig(); + + this.matrixInternal.copy( + createMatrixFromCameraInternal(option.cameraInternal, this.imgSize.x, this.imgSize.y), + ); + + // @ts-ignore + this.matrixExternal.set(...option.cameraExternal); + + // @ts-ignore + let matrix = new THREE.Matrix4().set(...option.cameraExternal); + matrix.premultiply(new THREE.Matrix4().makeScale(1, -1, -1)); + matrix.invert(); + matrix.decompose(this.camera.position, this.camera.quaternion, this.camera.scale); + this.camera.updateMatrixWorld(); + + this.matrix.copy(this.matrixInternal).multiply(this.camera.matrixWorldInverse); + + this.camera.projectionMatrix.copy(this.matrixInternal); + this.camera.projectionMatrixInverse.copy(this.camera.projectionMatrix).invert(); + // @ts-ignore + this.cameraHelper.update(); + + // clip + this.clipCamera.position.copy(this.camera.position); + this.clipCamera.quaternion.copy(this.camera.quaternion); + this.clipCamera.scale.copy(this.camera.scale); + this.clipCamera.updateMatrixWorld(); + // this.testFrustum(); + + this.render(); + } + + testFrustum() { + let frustum = new THREE.Frustum(); + frustum.setFromProjectionMatrix( + this.clipCamera.projectionMatrix.clone().multiply(this.clipCamera.matrixWorldInverse), + ); + let planeHelper = new THREE.PlaneHelper(frustum.planes[1], 100, 0xcccccc); + console.log('frustum.planes[1]', frustum.planes[1].normal); + + this.group.add(planeHelper); + } + + worldToImg(pos: THREE.Vector3, target?: THREE.Vector3) { + // let domElement = this.renderer.domElement; + target = target || pos; + + target.copy(pos); + + let matrix = get(THREE.Matrix4); + matrix.copy(this.camera.projectionMatrix); + matrix.multiply(this.camera.matrixWorldInverse); + + // pos.applyMatrix4(e.matrixWorld); + target.applyMatrix4(matrix); + target.x = ((target.x + 1) / 2) * this.imgSize.x; + target.y = (-(target.y - 1) / 2) * this.imgSize.y; + + return target; + } + + projectToImg(pos: THREE.Vector3, target?: THREE.Vector3) { + // let domElement = this.renderer.domElement; + target = target || pos; + pos.x = ((pos.x + 1) / 2) * this.imgSize.x; + pos.y = (-(pos.y - 1) / 2) * this.imgSize.y; + + return target; + } + + getBoxRect(object: Box) { + // let bbox = object.geometry.boundingBox as THREE.Box3; + + let box2dInfo = this.getBox2DBox(object); + let rectInfo = getMaxMinV2([...box2dInfo.positionsBack, ...box2dInfo.positionsFront]); + + // let matrix = get(THREE.Matrix4); + // matrix.copy(this.camera.projectionMatrix); + // matrix.multiply(this.camera.matrixWorldInverse); + // matrix.multiply(object.matrixWorld); + + // let box = get(THREE.Box3).copy(bbox); + // box.applyMatrix4(matrix); + + // this.projectToImg(box.max); + // this.projectToImg(box.min); + + // let rect = new Rect(); + let center = new THREE.Vector2().set( + (rectInfo.minX + rectInfo.maxX) / 2, + (rectInfo.minY + rectInfo.maxY) / 2, + ); + let size = new THREE.Vector2().set( + Math.abs(rectInfo.maxX - rectInfo.minX), + Math.abs(rectInfo.maxY - rectInfo.minY), + ); + return { center, size }; + } + + getBox2DBox(object: Box) { + let bbox = object.geometry.boundingBox as THREE.Box3; + + // let newBBox = bbox.clone().applyMatrix4(object.matrixWorld); + getPositions(bbox, positionsFrontV3, positionsBackV3); + // if (isInCamera(newBBox, this.camera)) { + // // console.log('isInCamera'); + // } + + let matrix = get(THREE.Matrix4).identity(); + // matrix.copy(this.camera.projectionMatrix); + // matrix.multiply(this.camera.matrixWorldInverse); + matrix.multiply(object.matrixWorld); + + positionsFrontV3.forEach((v) => { + v.applyMatrix4(matrix); + // if (v.z > 0) v.applyMatrix4(rotate180); + // v.applyMatrix4(this.camera.projectionMatrix); + // this.projectToImg(v); + }); + positionsBackV3.forEach((v) => { + v.applyMatrix4(matrix); + // if (v.z > 0) v.applyMatrix4(rotate180); + // v.applyMatrix4(this.camera.projectionMatrix); + // this.projectToImg(v); + }); + + reformProjectPoints(positionsFrontV3, positionsBackV3, this.clipCamera); + + // isInCamera([...positionsFrontV3, ...positionsBackV3], this.camera); + + // if (!checkProjectValidV2(positionsFrontV3, positionsBackV3)) { + // console.log('reformProjectPoints'); + // } + + positionsFrontV3.forEach((v) => { + v.applyMatrix4(this.camera.matrixWorldInverse); + v.applyMatrix4(this.camera.projectionMatrix); + this.projectToImg(v); + }); + positionsBackV3.forEach((v) => { + v.applyMatrix4(this.camera.matrixWorldInverse); + v.applyMatrix4(this.camera.projectionMatrix); + this.projectToImg(v); + }); + + // let points = [...positionsFrontV3, ...positionsBackV3]; + // console.log('z>0', points.filter((e) => e.z > 0).length); + + positionsFrontV2.forEach((v2, index) => { + let v3 = positionsFrontV3[index]; + v2.set(v3.x, v3.y); + }); + positionsBackV2.forEach((v2, index) => { + let v3 = positionsBackV3[index]; + v2.set(v3.x, v3.y); + }); + + return { positionsBack: positionsBackV2, positionsFront: positionsFrontV2 }; + } + + get2DObject() { + return this.pointCloud.getAnnotate2D(); + } + + get3DObject() { + return this.pointCloud.getAnnotate3D(); + } + + showMask(obj: AnnotateObject) { + return false; + } + + isHighlight(obj: AnnotateObject) { + return false; + } + + isRenderable(obj: Object2D) { + let flag1 = (this.renderId && obj.viewId === this.renderId) || obj.viewId === this.id; + + let flag2 = + (this.renderRect && obj instanceof Rect) || (this.renderBox2D && obj instanceof Box2D); + // (this.renderBox && obj instanceof Box); + + return obj.visible && flag1 && flag2; + } + + imgToDom(imgPos: THREE.Vector2 | THREE.Vector3) { + let pos = get(THREE.Vector3, 0); + pos.set(imgPos.x, imgPos.y, 0).applyMatrix4(this.transformMatrix); + imgPos.x = pos.x; + imgPos.y = pos.y; + } + + domToImg(imgPos: THREE.Vector2 | THREE.Vector3) { + let pos = get(THREE.Vector3, 0); + let invertMatrix = get(THREE.Matrix4, 0).copy(this.transformMatrix).invert(); + pos.set(imgPos.x, imgPos.y, 0).applyMatrix4(invertMatrix); + imgPos.x = pos.x; + imgPos.y = pos.y; + } + + getScale() { + return this.transformMatrix.elements[0] || 1; + } + + setViewport() { + let { imgSize, height: canvasHeight } = this; + let { renderer, clientRect, context } = this.proxy; + + // left-bottom corn + let pos = get(THREE.Vector2, 0).set(0, imgSize.y); + this.imgToDom(pos); + pos.y = canvasHeight - pos.y; + let scale = this.getScale(); + + let width = imgSize.x * scale; + let height = imgSize.y * scale; + + // proxy relative offset + let top = this.clientRect.top - clientRect.top; + let left = this.clientRect.left - clientRect.left; + let bottom = clientRect.bottom - this.clientRect.bottom; + + pos.x += left; + pos.y += bottom; + renderer.setViewport(pos.x, pos.y, width, height); + renderer.setScissor(pos.x, pos.y, width, height); + + // clip view region + context.beginPath(); + context.rect(left, top, this.clientRect.width, this.clientRect.height); + context.closePath(); + // context.stroke(); + context.clip(); + } + + render() { + if (!this.isEnable()) return; + this.proxy.render(); + } + + isViewRenderable() { + let { clientRect } = this.proxy; + let rect = this.container.getBoundingClientRect(); + this.clientRect = rect; + + let needRender = + rect.bottom >= clientRect.top && + rect.top <= clientRect.bottom && + rect.left <= clientRect.right && + rect.right >= clientRect.left; + + return needRender; + } + + updateTransform() { + let { clientRect } = this.proxy; + + const left = this.clientRect.left - clientRect.left; + const top = this.clientRect.top - clientRect.top; + + this.proxyOffset.set(left, top); + let offsetMatrix = get(THREE.Matrix4).makeTranslation(left, top, 0); + this.transformMatrix.copy(this.containerMatrix).multiply(this.fitMatrix); + this.proxyTransformMatrix.copy(offsetMatrix).multiply(this.transformMatrix); + } + + renderFrame() { + // console.log('renderFrame'); + if (!this.isViewRenderable()) return; + + this.dispatchEvent({ type: Event.RENDER_BEFORE }); + this.proxy.renderN++; + + this.updateSize(); + this.updateTransform(); + this.setViewport(); + + this.renderImage(); + this.renderObjects(); + + this.dispatchEvent({ type: Event.RENDER_AFTER }); + } + + renderObjects() { + if (!this.renderBox) return; + + let { groupPoints, selection, selectColor } = this.pointCloud; + let { renderer } = this.proxy; + let selection3Ds = selection.filter((e) => e instanceof THREE.Object3D); + let object3Ds = this.get3DObject(); + + if (this.renderBox && this.renderPoints && selection3Ds.length > 0) { + let groupPoint = groupPoints.children[0] as THREE.Points; + let box = selection[0] as Box; + box.updateMatrixWorld(); + if (!box.geometry.boundingBox) box.geometry.computeBoundingBox(); + + let bbox = box.geometry.boundingBox; + let material = groupPoint.material as PointsMaterial; + // groupPoint.material = this.materialPc; + + let oldDepthTest = material.depthTest; + let oldHasFilterBox = material.getUniforms('hasFilterBox'); + let oldType = material.getUniforms('boxInfo').type; + + material.depthTest = false; + material.setUniforms({ + hasFilterBox: 1, + boxInfo: { + type: 1, + min: bbox?.min, + max: bbox?.max, + color: selectColor, + matrix: this.boxInvertMatrix.copy(box.matrixWorld).invert(), + }, + }); + renderer.render(groupPoint, this.camera); + material.setUniforms({ hasFilterBox: oldHasFilterBox, boxInfo: { type: oldType } }); + material.depthTest = oldDepthTest; + + // this.renderer.render(groupPoint, this.camera); + // groupPoint.material = oldMaterial; + } + + if (this.renderBox) { + object3Ds.forEach((box) => { + this.renderBoxData(box as Box); + }); + selection3Ds.forEach((box) => { + this.renderBoxData(box as Box); + }); + } + } + + setContextTransform() { + let { context } = this.proxy; + let m = this.proxyTransformMatrix.elements; + // let m = this.transformMatrix.elements; + // `matrix(${m[0]},${m[1]},${m[4]},${m[5]},${m[12]},${m[13]})`; + context.setTransform(m[0], m[1], m[4], m[5], m[12], m[13]); + } + renderImage() { + let { width, height, imgSize } = this; + let { context } = this.proxy; + + if (!this.img) return; + + this.setContextTransform(); + context.drawImage(this.img, 0, 0, imgSize.x, imgSize.y); + } + + renderBoxData(box: Box) { + let { selectionMap, selectColor, highlightColor } = this.pointCloud; + let { renderer } = this.proxy; + let boxMaterial = box.material as THREE.LineBasicMaterial; + + let color = selectionMap[box.uuid] ? selectColor : box.color; + let highFlag = this.isHighlight(box); + color = highFlag ? highlightColor : color; + // let mask = this.showMask(box); + // if (mask) { + // renderBoxMask(box, this.renderer, this.camera); + // color = this.selectColor; + // } + + if (box.dashed) { + let dashedMaterial = box.dashedMaterial; + dashedMaterial.color = color; + box.material = dashedMaterial; + renderer.render(box, this.camera); + box.material = boxMaterial; + } else { + let oldColor = boxMaterial.color; + boxMaterial.color = color; + renderer.render(box, this.camera); + boxMaterial.color = oldColor; + } + } +} + +function getPositions( + box: THREE.Box3, + positionsFront: THREE.Vector3[], + positionsBack: THREE.Vector3[], +) { + // front + positionsFront[0].set(box.max.x, box.min.y, box.max.z); + positionsFront[1].set(box.max.x, box.min.y, box.min.z); + positionsFront[2].set(box.max.x, box.max.y, box.min.z); + positionsFront[3].set(box.max.x, box.max.y, box.max.z); + + // back + positionsBack[0].set(box.min.x, box.min.y, box.max.z); + positionsBack[1].set(box.min.x, box.min.y, box.min.z); + positionsBack[2].set(box.min.x, box.max.y, box.min.z); + positionsBack[3].set(box.min.x, box.max.y, box.max.z); +} diff --git a/frontend/text-tool/src/packages/pc-render/renderView/MainRenderView.ts b/frontend/text-tool/src/packages/pc-render/renderView/MainRenderView.ts new file mode 100644 index 00000000..34e1c328 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/renderView/MainRenderView.ts @@ -0,0 +1,268 @@ +import * as THREE from 'three'; +import Box from '../objects/Box'; +// import { Event } from '../config'; +import Render from './Render'; +import PointCloud from '../PointCloud'; +import PointsMaterial from '../material/PointsMaterial'; +import TWEEN, { Tween } from '@tweenjs/tween.js'; +// import Action from '../action/Action'; +import OrbitControlsAction from '../action/OrbitControlsAction'; + +// const defaultActions = ['transform-control', 'select', 'create-obj']; +const defaultActions = [ + 'orbit-control', + 'transform-control', + 'select', + 'create-obj', + 'view-helper', +]; +export type wayToFocus = 'zTop' | 'auto'; + +export default class MainRenderView extends Render { + container: HTMLDivElement; + pointCloud: PointCloud; + width: number; + height: number; + renderer: THREE.WebGLRenderer; + camera: THREE.PerspectiveCamera; + raycaster: THREE.Raycaster; + tween: Tween | null = null; + // helper: THREE.BoxHelper; + // material: PointsMaterial; + selectColor: THREE.Color = new THREE.Color(1, 0, 0); + backgroundColor: THREE.Color = new THREE.Color(0, 0, 0); + boxInvertMatrix: THREE.Matrix4 = new THREE.Matrix4(); + + constructor(container: HTMLDivElement, pointCloud: PointCloud, config: any = {}) { + super(config.name || ''); + + this.container = container; + this.pointCloud = pointCloud; + + this.width = this.container.clientWidth; + this.height = this.container.clientHeight; + + // renderer + this.renderer = new THREE.WebGLRenderer({ antialias: true }); + this.renderer.setPixelRatio(pointCloud.pixelRatio); + this.renderer.setSize(this.width, this.height); + this.renderer.setClearColor(this.backgroundColor); + this.renderer.sortObjects = false; + this.renderer.autoClear = false; + this.container.appendChild(this.renderer.domElement); + + this.camera = new THREE.PerspectiveCamera(35, this.width / this.height, 1, 30000); + // let aspect = this.width / this.height; + // let depth = 100; + // let fov_y = 35; + // let height_ortho = depth * 2 * Math.atan((fov_y * (Math.PI / 180)) / 2); + // let width_ortho = height_ortho * aspect; + // this.camera = new THREE.OrthographicCamera(width_ortho / -2, width_ortho / 2, height_ortho / 2, height_ortho / -2, 0.01, 30000); + + this.pointCloud.scene.add(this.camera); + this.camera.position.set(0, 0, 100); + this.camera.up.set(0, 0, 1); + this.camera.lookAt(0, 0, 0); + + this.raycaster = new THREE.Raycaster(); + + this.setActions(config.actions || defaultActions); + + // @ts-ignore + window.mainview = this; + } + + getObjectByCanvas(canvasPos: THREE.Vector2): THREE.Object3D | null { + let { clientHeight: height, clientWidth: width } = this.renderer.domElement; + let x = (canvasPos.x / width) * 2 - 1; + let y = (-canvasPos.y / height) * 2 + 1; + + let annotate3D = this.pointCloud.getAnnotate3D(); + this.raycaster.setFromCamera({ x, y }, this.camera); + const intersects = this.raycaster.intersectObjects(annotate3D); + return intersects.length > 0 ? intersects[0].object : null; + } + + canvasToWorld(p: THREE.Vector2) { + let { clientHeight: height, clientWidth: width } = this.renderer.domElement; + + let ground = this.pointCloud.ground.plane; + let x = (p.x / width) * 2 - 1; + let y = (-p.y / height) * 2 + 1; + // let projectP = this.getProjectPos(e); + this.raycaster.setFromCamera({ x, y }, this.camera); + + let intersectP = new THREE.Vector3(); + this.raycaster.ray.intersectPlane(ground, intersectP); + return intersectP; + } + + focusPositionByZTop(position: THREE.Vector3) { + if (this.tween) this.tween.stop(); + + this.raycaster.setFromCamera({ x: 0, y: 0 }, this.camera); + let startFocus = new THREE.Vector3(); + let focus = new THREE.Vector3(); + this.raycaster.ray.intersectPlane(this.pointCloud.ground.plane, startFocus); + + const cameraPos = this.camera.position.clone(); + let pos = new THREE.Vector3(); + // let object = selection[0]; + pos.copy(position); + pos.z += 70; + const tween = new TWEEN.Tween(cameraPos) + .to(pos, 400) + .onUpdate((obj, elapsed) => { + if (elapsed === 1) return false; + let action = this.getAction('orbit-control') as OrbitControlsAction; + if (action) { + focus.copy(position).sub(startFocus).multiplyScalar(elapsed).add(startFocus); + action.control.target.copy(focus); + this.camera.position.copy(obj); + action.control.update(); + } + this.render(); + }) + .onComplete(() => { + this.tween = null; + }) + .start(); + + this.tween = tween; + } + focusPosition(position: THREE.Vector3, way: wayToFocus = 'zTop') { + switch (way) { + default: + case 'zTop': + this.focusPositionByZTop(position); + break; + case 'auto': + this.focusPositionByAuto(position); + break; + } + } + focusPositionByAuto(position: THREE.Vector3) { + if (this.tween) this.tween.stop(); + + this.raycaster.setFromCamera({ x: 0, y: 0 }, this.camera); + let startFocus = new THREE.Vector3(); + let focus = new THREE.Vector3(); + this.raycaster.ray.intersectPlane(this.pointCloud.ground.plane, startFocus); + + let pos = new THREE.Vector3(); + // let object = selection[0]; + pos.copy(this.camera.position).sub(position).setLength(30).add(position); + pos.z = Math.max(10, Math.abs(pos.z)); + // pos.z += 70; + const tween = new TWEEN.Tween(this.camera.position) + .to(pos, 400) + .onUpdate((obj, elapsed) => { + // if (elapsed === 1) return false; + let action = this.getAction('orbit-control') as OrbitControlsAction; + if (action) { + focus.copy(position).sub(startFocus).multiplyScalar(elapsed).add(startFocus); + action.control.target.copy(focus); + action.control.update(); + } + this.render(); + }) + .onComplete(() => { + this.tween = null; + }) + .start(); + + this.tween = tween; + } + updateSize() { + let width = this.container.clientWidth || 10; + let height = this.container.clientHeight || 10; + + if (width !== this.width || height !== this.height) { + this.width = width; + this.height = height; + this.renderer.setSize(this.width, this.height); + this.camera.aspect = this.width / this.height; + this.camera.updateProjectionMatrix(); + } + } + + renderFrame() { + // let { scene, groupPoints } = this.pointCloud; + let { groupPoints, scene, selection, annotate3D, selectionMap } = this.pointCloud; + + this.updateSize(); + + this.renderer.clear(true, true, true); + + let object3d = selection.find((item) => item instanceof Box); + + if (object3d && object3d.visible) { + // render points + let groupPoint = groupPoints.children[0] as THREE.Points; + let box = object3d as Box; + box.updateMatrixWorld(); + // if (!box.geometry.boundingBox) box.geometry.computeBoundingBox(); + + let bbox = box.geometry.boundingBox as THREE.Box3; + let material = groupPoint.material as PointsMaterial; + + let oldHasFilterBox = material.getUniforms('hasFilterBox'); + let oldType = material.getUniforms('boxInfo').type; + material.setUniforms({ + hasFilterBox: 1, + boxInfo: { + type: 0, + min: bbox.min, + max: bbox.max, + color: this.selectColor, + matrix: this.boxInvertMatrix.copy(box.matrixWorld).invert(), + }, + }); + + annotate3D.visible = false; + this.renderer.render(scene, this.camera); + + material.setUniforms({ hasFilterBox: oldHasFilterBox, boxInfo: { type: oldType } }); + + annotate3D.visible = true; + annotate3D.children.forEach((box) => { + if (box === object3d) return; + this.renderBox(box as Box); + }); + + // render select + // let select = selection[0]; + this.renderBox(box, this.selectColor); + } else { + annotate3D.visible = false; + this.renderer.render(scene, this.camera); + annotate3D.visible = true; + annotate3D.children.forEach((box) => { + this.renderBox(box as Box, selectionMap[box.uuid] ? this.selectColor : undefined); + }); + } + } + + renderBox(box: Box, color?: THREE.Color) { + let boxMaterial = box.material as THREE.LineBasicMaterial; + + if (box.dashed) { + let dashedMaterial = box.dashedMaterial; + dashedMaterial.color = color || box.color; + box.material = dashedMaterial; + this.renderer.render(box, this.camera); + box.material = boxMaterial; + } else { + let oldColor = boxMaterial.color; + boxMaterial.color = color || box.color; + this.renderer.render(box, this.camera); + boxMaterial.color = oldColor; + } + } + + setBackgroundColor(backgroundColor: THREE.Color) { + this.backgroundColor = backgroundColor; + this.renderer.setClearColor(this.backgroundColor); + this.render(); + } +} diff --git a/frontend/text-tool/src/packages/pc-render/renderView/Render.ts b/frontend/text-tool/src/packages/pc-render/renderView/Render.ts new file mode 100644 index 00000000..6e5d7093 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/renderView/Render.ts @@ -0,0 +1,105 @@ +import * as THREE from 'three'; +import Action from '../action/Action'; +import Actions, { ActionCtr } from '../action'; +import { Event } from '../config'; + +// type ActionCtr = new () => Action; + +export default class Render extends THREE.EventDispatcher { + id: string; + renderId: string = ''; + actions: string[] = []; + name: string = ''; + enabled: boolean = true; + actionMap: { [key: string]: Action } = {}; + + private renderTimer: number = 0; + + constructor(name?: string) { + super(); + + if (name) this.name = name; + this.id = THREE.MathUtils.generateUUID(); + } + + init() {} + + destroy() {} + + /** + * @param enabled + */ + toggle(enabled: boolean) { + this.enabled = enabled; + + if (enabled) this.render(); + } + + isEnable(): boolean { + return this.enabled; + } + + getAction(name: string) { + return this.actionMap[name]; + } + setActions(actionNames: string[]) { + this.actions = actionNames; + actionNames.forEach((name) => { + let action: Action = new (Actions[name] as ActionCtr)(this); + action.init(); + this.actionMap[name] = action; + }); + } + disableAction(actionName?: string | string[]) { + let actions: Action[] = []; + let names = []; + if (!actionName) { + names = this.actions; + } else { + names = Array.isArray(actionName) ? actionName : [actionName]; + names.forEach((name) => { + let action = this.actionMap[name]; + if (action) actions.push(action); + }); + } + + actions.forEach((action) => { + action.toggle(false); + }); + } + + enableAction(actionName?: string | string[]) { + let actions: Action[] = []; + let names = []; + if (!actionName) { + names = this.actions; + } else { + names = Array.isArray(actionName) ? actionName : [actionName]; + names.forEach((name) => { + let action = this.actionMap[name]; + if (action) actions.push(action); + }); + } + + actions.forEach((action) => { + action.toggle(true); + }); + } + + _render() { + this.dispatchEvent({ type: Event.RENDER_BEFORE }); + this.renderFrame(); + this.renderTimer = 0; + this.dispatchEvent({ type: Event.RENDER_AFTER }); + } + + renderFrame() { + throw new Error('must implement method renderFrame;'); + } + + render() { + if (!this.isEnable()) return; + if (this.renderTimer) return; + this.renderTimer = requestAnimationFrame(this._render.bind(this)); + } +} diff --git a/frontend/text-tool/src/packages/pc-render/renderView/SideRenderView.ts b/frontend/text-tool/src/packages/pc-render/renderView/SideRenderView.ts new file mode 100644 index 00000000..8f48cd96 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/renderView/SideRenderView.ts @@ -0,0 +1,334 @@ +import * as THREE from 'three'; +import Box from '../objects/Box'; +import Render from './Render'; +import PointCloud from '../PointCloud'; +import { Event } from '../config'; +import PointsMaterial from '../material/PointsMaterial'; +import * as _ from 'lodash'; + +export let axisUpInfo = { + x: { + yAxis: { axis: 'z', dir: new THREE.Vector3(0, 0, 1) }, + xAxis: { axis: 'y', dir: new THREE.Vector3(0, 1, 0) }, + }, + '-x': { + yAxis: { axis: 'z', dir: new THREE.Vector3(0, 0, 1) }, + xAxis: { axis: 'y', dir: new THREE.Vector3(0, -1, 0) }, + }, + z: { + yAxis: { axis: 'x', dir: new THREE.Vector3(1, 0, 0) }, + xAxis: { axis: 'y', dir: new THREE.Vector3(0, -1, 0) }, + }, + // '-z': { + // yAxis: { axis: 'y', dir: new THREE.Vector3(0, 1, 0) }, + // xAxis: { axis: 'x', dir: new THREE.Vector3(-1, 0, 0) }, + // }, + y: { + yAxis: { axis: 'z', dir: new THREE.Vector3(0, 0, 1) }, + xAxis: { axis: 'x', dir: new THREE.Vector3(-1, 0, 0) }, + }, + '-y': { + yAxis: { axis: 'z', dir: new THREE.Vector3(0, 0, 1) }, + xAxis: { axis: 'x', dir: new THREE.Vector3(1, 0, 0) }, + }, +}; + +export type axisType = keyof typeof axisUpInfo; +// export type axisType = 'x' | 'y' | 'z' | '-x' | '-y'; + +// const defaultActions: string[] = []; +const defaultActions = ['resize-translate']; + +export default class SideRenderView extends Render { + container: HTMLDivElement; + pointCloud: PointCloud; + width: number; + height: number; + renderer: THREE.WebGLRenderer; + camera: THREE.OrthographicCamera; + cameraHelper?: THREE.CameraHelper; + object: Box | null; + projectRect: THREE.Box3; + axis: axisType; + alignAxis: THREE.Vector3; + paddingPercent: number; + needFit: boolean = true; + enableFit: boolean = true; + // material: THREE.ShaderMaterial; + selectColor: THREE.Color = new THREE.Color(0, 1, 0); + boxInvertMatrix: THREE.Matrix4 = new THREE.Matrix4(); + zoom: number = 1; + cameraOffset: THREE.Vector3 = new THREE.Vector3(); + + constructor(container: HTMLDivElement, pointCloud: PointCloud, config = {} as any) { + super(config.name || ''); + + let { axis = 'z', paddingPercent = 1 } = config; + + this.container = container; + this.pointCloud = pointCloud; + + this.object = null; + this.projectRect = new THREE.Box3(); + this.axis = axis; + this.alignAxis = new THREE.Vector3(); + this.setAxis(axis); + + // this.resizing = false; + this.paddingPercent = paddingPercent; + + this.width = this.container.clientWidth; + this.height = this.container.clientHeight; + + // renderer + this.renderer = new THREE.WebGLRenderer({ antialias: true }); + this.renderer.autoClear = false; + this.renderer.sortObjects = false; + this.renderer.setPixelRatio(pointCloud.pixelRatio); + this.renderer.setSize(this.width, this.height); + this.container.appendChild(this.renderer.domElement); + + this.camera = new THREE.OrthographicCamera(-2, 2, 2, -2, 0, 10); + this.pointCloud.scene.add(this.camera); + // this.camera.position.set(-0, 0, -100); + // this.camera.up.set(0, 1, 0); + + // helper + let camera = this.camera; + // camera.lookAt(0, 0, 0); + const helper = new THREE.CameraHelper(camera); + // this.pointCloud.scene.add(helper); + this.cameraHelper = helper; + + // this.renderer.setClearColor(new THREE.Color(0.1, 0.1, 0.1)); + this.setActions(config.actions || defaultActions); + this.initEvent(); + // this.material = this.createMaterial(); + // this.initDom(); + + // @ts-ignore + window.subView = this; + } + + initEvent() { + this.pointCloud.addEventListener(Event.SELECT, () => { + let object = this.pointCloud.selection.find((annotate) => annotate instanceof Box); + if (object) { + this.enableFit = true; + this.zoom = 1; + this.fitObject(object as Box); + } else { + this.object = null; + } + this.render(); + }); + + this.pointCloud.addEventListener(Event.OBJECT_TRANSFORM, (e) => { + let object = e.data.object; + if ( + object && + object instanceof THREE.Object3D && + object === this.object && + this.needFit && + this.enableFit + ) { + this.fitObject(); + this.render(); + } + }); + } + + setAxis(axis: axisType) { + this.axis = axis; + this.alignAxis.set(0, 0, 0); + + let axisValue = this.axis.length === 2 ? this.axis[1] : this.axis[0]; + let isInverse = this.axis.length === 2; + this.alignAxis[axisValue as 'x' | 'y' | 'z'] = isInverse ? -0.5 : 0.5; + + if (this.object) this.fitObject(); + + this.render(); + } + + cameraToCanvas(pos: THREE.Vector3) { + pos.applyMatrix4(this.camera.projectionMatrix); + pos.x = ((pos.x + 1) / 2) * this.width; + pos.y = (-(pos.y - 1) / 2) * this.height; + return pos; + } + + canvasToCamera(pos: THREE.Vector3) { + // pos.applyMatrix4(this.camera.projectionMatrix.clone().invert()); + pos.x = (pos.x / this.width) * 2 - 1; + pos.y = ((-1 * pos.y) / this.height) * 2 + 1; + + pos.x *= this.camera.right - this.camera.left; + pos.y *= this.camera.top - this.camera.bottom; + return pos; + } + + updateProjectRect() { + if (!this.object) return; + + let { axis, object, camera } = this; + + camera.updateMatrixWorld(); + object.updateMatrixWorld(); + + if (!object.geometry.boundingBox) object.geometry.computeBoundingBox(); + let bbox = object.geometry.boundingBox as any as THREE.Box3; + + let minProject = new THREE.Vector3().copy(bbox.min); + let maxProject = new THREE.Vector3().copy(bbox.max); + + minProject.applyMatrix4(object.matrixWorld).applyMatrix4(camera.matrixWorldInverse); + maxProject.applyMatrix4(object.matrixWorld).applyMatrix4(camera.matrixWorldInverse); + + let min = new THREE.Vector3(); + let max = new THREE.Vector3(); + + let xMin = Math.min(minProject.x, maxProject.x); + let xMax = Math.max(minProject.x, maxProject.x); + let yMin = Math.min(minProject.y, maxProject.y); + let yMax = Math.max(minProject.y, maxProject.y); + let zMin = Math.min(minProject.z, maxProject.z); + let zMax = Math.max(minProject.z, maxProject.z); + + min.set(xMin, yMin, zMin); + max.set(xMax, yMax, zMax); + + this.projectRect.min.copy(min); + this.projectRect.max.copy(max); + // = { min, max }; + // return ; + } + + fitObject(object?: Box) { + // console.log('fitObject'); + if (object) this.object = object; + + object = this.object as Box; + if (!object) return; + + object.updateMatrixWorld(); + + let temp = new THREE.Vector3(); + temp.copy(this.alignAxis); + temp.applyMatrix4(object.matrixWorld); + this.camera.position.copy(temp); + + temp.copy(axisUpInfo[this.axis].yAxis.dir) + .applyMatrix4(object.matrixWorld) + .sub(new THREE.Vector3().applyMatrix4(object.matrixWorld)); + this.camera.up.copy(temp); + + temp.set(0, 0, 0); + temp.applyMatrix4(object.matrixWorld); + this.camera.lookAt(temp); + + this.updateProjectRect(); + this.updateCameraProject(); + // this._render(); + // this.updateDom(); + // this.render(); + } + + updateCameraProject() { + let { projectRect } = this; + let rectWidth = projectRect.max.x - projectRect.min.x; + let rectHeight = projectRect.max.y - projectRect.min.y; + let aspect = this.width / this.height; + + // debugger + let cameraW, cameraH; + let padding = Math.min(rectWidth, rectHeight) * this.paddingPercent; + // let padding = (200 * rectWidth) / this.width; + cameraW = Math.max(rectWidth + padding, (rectHeight + padding) * aspect); + cameraH = Math.max(rectHeight + padding, (rectWidth + padding) / aspect); + + this.camera.left = (-cameraW / 2) * this.zoom; + this.camera.right = (cameraW / 2) * this.zoom; + this.camera.top = (cameraH / 2) * this.zoom; + this.camera.bottom = (-cameraH / 2) * this.zoom; + // debugger + this.camera.far = projectRect.max.z - projectRect.min.z; + this.camera.updateProjectionMatrix(); + + // this.camera.position.add(this.cameraOffset); + // this.camera.updateMatrixWorld(); + // this.camera.far = 0; + this.cameraHelper?.update(); + } + + updateSize() { + let width = this.container.clientWidth || 10; + let height = this.container.clientHeight || 10; + + if (width !== this.width || height !== this.height) { + this.width = width; + this.height = height; + this.renderer.setSize(this.width, this.height); + // this.camera.aspect = this.width / this.height; + // this.camera.updateProjectionMatrix(); + } + } + + // render + renderFrame() { + // console.log('renderFrame'); + let { groupPoints, scene, selection } = this.pointCloud; + + this.updateSize(); + // if(this.renderTimer) return; + this.renderer.clear(true, true, true); + + if (groupPoints.children.length === 0) return; + + let hasObject3D = selection.find((e) => e instanceof THREE.Object3D); + + if (selection.length > 0 && hasObject3D) { + // render points + let groupPoint = groupPoints.children[0] as THREE.Points; + let box = hasObject3D as Box; + box.updateMatrixWorld(); + // if (!box.geometry.boundingBox) box.geometry.computeBoundingBox(); + + let bbox = box.geometry.boundingBox as THREE.Box3; + let material = groupPoint.material as PointsMaterial; + + let oldDepthTest = material.depthTest; + let oldHasFilterBox = material.getUniforms('hasFilterBox'); + let oldType = material.getUniforms('boxInfo').type; + + material.depthTest = false; + material.setUniforms({ + hasFilterBox: 1, + boxInfo: { + type: 0, + min: bbox.min, + max: bbox.max, + color: this.selectColor, + matrix: this.boxInvertMatrix.copy(box.matrixWorld).invert(), + }, + }); + this.renderer.render(groupPoint, this.camera); + + material.setUniforms({ hasFilterBox: oldHasFilterBox, boxInfo: { type: oldType } }); + material.depthTest = oldDepthTest; + + // render box + selection.forEach((object) => { + if (object instanceof THREE.Object3D) { + this.renderer.render(object, this.camera); + } + }); + } else { + this.renderer.render(groupPoints, this.camera); + } + + this.updateProjectRect(); + // console.log('renderFrame'); + // this.updateDom(); + } +} diff --git a/frontend/text-tool/src/packages/pc-render/type.ts b/frontend/text-tool/src/packages/pc-render/type.ts new file mode 100644 index 00000000..17d41dcb --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/type.ts @@ -0,0 +1,33 @@ +import * as THREE from 'three'; + +export type { AnnotateObject, Vector2Of4 } from './objects'; + +export interface Intersect { + object: THREE.Object3D; + distance: number; + point: THREE.Vector3; +} + +export interface ITransform { + rotation?: THREE.Euler; + scale?: THREE.Vector3; + position?: THREE.Vector3; +} + +export interface IRenderViewConfig { + name?: string; + actions?: T[]; + [key: string]: any; +} + +export interface ICameraInternal { + fx: number; + fy: number; + cx: number; + cy: number; +} + +export enum AnnotateType { + ANNOTATE_3D = 'annotate_3d', + ANNOTATE_2D = 'annotate_2d', +} diff --git a/frontend/text-tool/src/packages/pc-render/utils/index.ts b/frontend/text-tool/src/packages/pc-render/utils/index.ts new file mode 100644 index 00000000..b2dcda03 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/utils/index.ts @@ -0,0 +1,278 @@ +import { ICameraInternal } from '../type'; +import * as THREE from 'three'; +import { Box, Box2D, Rect } from '../objects'; +import Image2DRenderView from '../renderView/Image2DRenderView'; + +let near = 0.01; +let far = 10000; +export function createMatrixFromCameraInternal( + option: ICameraInternal, + w: number, + h: number, +): THREE.Matrix4 { + let { fx, fy, cy, cx } = option; + // return new THREE.Matrix4().set( + // 2*fx / w, 0, 1 - 2*cx / w, 0, + // 0, 2*fy / h, 2*cy / h - 1, 0, + // 0, 0, (near + far) / (near - far), 2*far*near / (near - far), + // 0, 0, -1.0, 0); + + return new THREE.Matrix4().set( + (2 * fx) / w, + 0, + 1 - (2 * cx) / w, + 0, + 0, + (2 * fy) / h, + (2 * cy) / h - 1, + 0, + 0, + 0, + (near + far) / (near - far), + (2 * far * near) / (near - far), + 0, + 0, + -1, + 0, + ); +} + +export function isPointInRect(pos: THREE.Vector2, rect: THREE.Vector2[]) { + let A = rect[0]; + let B = rect[1]; + let C = rect[2]; + let D = rect[3]; + let a = (B.x - A.x) * (pos.y - A.y) - (B.y - A.y) * (pos.x - A.x); + let b = (C.x - B.x) * (pos.y - B.y) - (C.y - B.y) * (pos.x - B.x); + let c = (D.x - C.x) * (pos.y - C.y) - (D.y - C.y) * (pos.x - C.x); + let d = (A.x - D.x) * (pos.y - D.y) - (A.y - D.y) * (pos.x - D.x); + if ((a > 0 && b > 0 && c > 0 && d > 0) || (a < 0 && b < 0 && c < 0 && d < 0)) { + return true; + } + return false; +} + +export function isLeft(e: MouseEvent) { + return e.button === 0; +} + +export function isRight(e: MouseEvent) { + return e.button === 2; +} +export function getMaxMinV2(positions: THREE.Vector2[]) { + let minX = Infinity; + let maxX = -Infinity; + let minY = Infinity; + let maxY = -Infinity; + // let minZ = Infinity; + // let maxZ = -Infinity; + positions.forEach((pos) => { + if (pos.x < minX) minX = pos.x; + if (pos.x > maxX) maxX = pos.x; + if (pos.y < minY) minY = pos.y; + if (pos.y > maxY) maxY = pos.y; + // if (pos.z < minZ) minZ = pos.z; + // if (pos.z > maxZ) maxZ = pos.z; + }); + return { minX, maxX, minY, maxY }; +} + +let line = new THREE.Line3(); +let intersect = new THREE.Vector3(); +let dir = new THREE.Vector3(); +let frustum = new THREE.Frustum(); +export function reformProjectPoints( + positionsFrontV3: THREE.Vector3[], + positionsBackV3: THREE.Vector3[], + camera: THREE.PerspectiveCamera, +) { + frustum.setFromProjectionMatrix( + camera.projectionMatrix.clone().multiply(camera.matrixWorldInverse), + ); + // let n = 0; + + // console.log(JSON.parse(JSON.stringify([...positionsFrontV3, ...positionsBackV3]))); + + // console.log('frustum.planes[0]', frustum.planes[0].normal); + traverseLine(positionsFrontV3, positionsBackV3, (p1, p2) => { + // if (intersectHandle(p1, p2, frustum.planes[0])) n++; + intersectHandle(p1, p2, frustum.planes[0]); + }); + + traverseLine(positionsFrontV3, positionsBackV3, (p1, p2) => { + intersectHandle(p1, p2, frustum.planes[1]); + // if (intersectHandle(p1, p2, frustum.planes[1])) n++; + }); + + // console.log({ n }); +} +function traverseLine( + positionsFrontV3: THREE.Vector3[], + positionsBackV3: THREE.Vector3[], + callback: (p1: THREE.Vector3, p2: THREE.Vector3) => void, +) { + positionsFrontV3.forEach((front, index) => { + let back = positionsBackV3[index]; + callback(front, back); + }); + + positionsFrontV3.forEach((p1, index) => { + let p2 = positionsFrontV3[(index + 1) % 4]; + callback(p1, p2); + }); + + positionsBackV3.forEach((p1, index) => { + let p2 = positionsBackV3[(index + 1) % 4]; + callback(p1, p2); + }); +} + +function intersectHandle(start: THREE.Vector3, end: THREE.Vector3, plane: THREE.Plane) { + // 同一个平面不处理 + let dotV = dir.copy(end).sub(start).dot(plane.normal); + + // console.log(JSON.parse(JSON.stringify(start)), JSON.parse(JSON.stringify(end))); + // console.log('dotV:', dotV); + if (Math.abs(dotV) < 0.000001) return; + + line.start.copy(start); + line.end.copy(end); + + let flag = plane.intersectLine(line, intersect); + // console.log('flag', flag); + if (flag) { + dir.copy(start).sub(intersect); + if (dir.dot(plane.normal) < 0) { + start.copy(intersect); + } + + dir.copy(end).sub(intersect); + if (dir.dot(plane.normal) < 0) { + end.copy(intersect); + } + } + + return flag; +} + +let v1 = new THREE.Vector3(); +let v2 = new THREE.Vector3(); +let v3 = new THREE.Vector3(); +let v4 = new THREE.Vector3(); +let v5 = new THREE.Vector3(); +let v6 = new THREE.Vector3(); +let v7 = new THREE.Vector3(); +let v8 = new THREE.Vector3(); +export function isBoxInImage(object: Box, view: Image2DRenderView) { + let box = object.geometry.boundingBox as THREE.Box3; + v1.set(box.max.x, box.min.y, box.max.z); + v2.set(box.max.x, box.min.y, box.min.z); + v3.set(box.max.x, box.max.y, box.min.z); + v4.set(box.max.x, box.max.y, box.max.z); + + v5.set(box.min.x, box.min.y, box.max.z); + v6.set(box.min.x, box.min.y, box.min.z); + v7.set(box.min.x, box.max.y, box.min.z); + v8.set(box.min.x, box.max.y, box.max.z); + + let positions = [v1, v2, v3, v4, v5, v6, v7, v8]; + let offset = 10; + + for (let i = 0; i < positions.length; i++) { + let pos = positions[i]; + pos.applyMatrix4(object.matrixWorld); + view.worldToImg(pos); + if ( + pos.x > offset && + pos.x < view.imgSize.x && + pos.y > offset && + pos.y < view.imgSize.y && + Math.abs(pos.z) < 1 + ) { + return true; + } + } + + return false; +} + +let temp_1 = new THREE.Vector2(); +let temp_2 = new THREE.Vector2(); +let temp_3 = new THREE.Vector2(); + +export function renderRect( + context: CanvasRenderingContext2D, + obj: Rect, + option: { lineWidth?: number; color?: string } = {}, +) { + let pos = temp_1; + let temp = temp_2; + let size = temp_3; + let { lineWidth = 1, color = '#FF0000' } = option; + + context.strokeStyle = color; + context.lineWidth = lineWidth; + context.setLineDash(obj.dashed ? [5, 5] : []); + + context.beginPath(); + temp.copy(obj.size).multiplyScalar(0.5); + pos.copy(obj.center).sub(temp); + size.copy(obj.size); + context.strokeRect(pos.x, pos.y, size.x, size.y); + context.stroke(); +} + +export function renderBox2D( + context: CanvasRenderingContext2D, + obj: Box2D, + option: { lineWidth?: number; color?: string } = {}, +) { + let { positions1, positions2 } = obj; + let { lineWidth = 1, color = '#FF0000' } = option; + + context.strokeStyle = color; + context.lineWidth = lineWidth; + + // back + context.globalAlpha = 1; + context.setLineDash([5, 5]); + let position = positions2; + let pos = temp_2.copy(position[0]); + context.beginPath(); + // pos.multiply(scaleSize); + context.moveTo(pos.x, pos.y); + position.forEach((e, index) => { + pos.copy(position[(index + 1) % 4]); + // pos.multiply(scaleSize); + context.lineTo(pos.x, pos.y); + }); + context.closePath(); + context.stroke(); + + // front + context.setLineDash(obj.dashed ? [5, 5] : []); + position = positions1; + pos = temp_2.copy(position[0]); + context.beginPath(); + // pos.multiply(scaleSize); + context.moveTo(pos.x, pos.y); + position.forEach((e, index) => { + pos.copy(position[(index + 1) % 4]); + // pos.multiply(scaleSize); + context.lineTo(pos.x, pos.y); + }); + context.closePath(); + context.stroke(); + + // line + let pos1 = temp_3; + context.beginPath(); + positions1.forEach((e, index) => { + pos.copy(positions1[index]); + pos1.copy(positions2[index]); + + context.moveTo(pos.x, pos.y); + context.lineTo(pos1.x, pos1.y); + }); + context.stroke(); +} diff --git a/frontend/text-tool/src/packages/pc-render/utils/tempVar.ts b/frontend/text-tool/src/packages/pc-render/utils/tempVar.ts new file mode 100644 index 00000000..6f8cb427 --- /dev/null +++ b/frontend/text-tool/src/packages/pc-render/utils/tempVar.ts @@ -0,0 +1,17 @@ +const tempMap = new Map(); + +export function get(type: new () => T, index: number = 0): T { + let tempArr = tempMap.get(type); + if (!tempArr) { + tempArr = []; + tempMap.set(type, tempArr); + } + + let value = tempArr[index]; + if (!value) { + tempArr[index] = new type(); + value = tempArr[index]; + } + + return value; +} diff --git a/frontend/text-tool/src/pages/execute.ts b/frontend/text-tool/src/pages/execute.ts new file mode 100644 index 00000000..2a0f0439 --- /dev/null +++ b/frontend/text-tool/src/pages/execute.ts @@ -0,0 +1,67 @@ +import { IAction, IPageHandler } from '../type'; +import { useInjectEditor } from '../state'; +import modes from '../config/mode'; +import useTool from '../hook/useTool'; +import { Box } from 'pc-render'; +// import BSError from '../common/BSError'; + +export function execute(): IPageHandler { + let editor = useInjectEditor(); + + let { + loadClasses, + loadRecord, + loadDataSetInfo, + loadModels, + loadDateSetClassification, + } = useTool(); + + // datasetId=30093&dataId=352734&type=readOnly + + async function init() { + let { state, bsState } = editor; + if (!bsState.query.recordId) { + editor.showMsg('error', editor.lang('invalid-query')); + return; + } + + // set mode + editor.setMode(modes.execute); + + editor.showLoading(true); + try { + // get data list by record id + await loadRecord(); + // await loadDataSetInfo(); + await Promise.all([ + // load Classification / class /model + loadDateSetClassification(), + loadClasses(), + // loadModels(), + ]); + // load first data + await editor.loadFrame(0, false); + focusObject(); + } catch (error: any) { + editor.handleErr(error, editor.lang('load-error')); + } + + editor.showLoading(false); + } + + function focusObject() { + } + + function onAction(action: IAction) { + switch (action) { + case 'save': + editor.saveObject(); + break; + } + } + + return { + init, + onAction, + }; +} diff --git a/frontend/text-tool/src/pages/index.ts b/frontend/text-tool/src/pages/index.ts new file mode 100644 index 00000000..3b77ad10 --- /dev/null +++ b/frontend/text-tool/src/pages/index.ts @@ -0,0 +1,2 @@ +export * from './execute'; +export * from './view'; diff --git a/frontend/text-tool/src/pages/view.ts b/frontend/text-tool/src/pages/view.ts new file mode 100644 index 00000000..8536613c --- /dev/null +++ b/frontend/text-tool/src/pages/view.ts @@ -0,0 +1,65 @@ +import { IAction, IPageHandler } from '../type'; +import { IFrame } from 'pc-editor'; +import { useInjectEditor } from '../state'; +import modes from '../config/mode'; +import useTool from '../hook/useTool'; +import { BSError } from 'pc-editor'; + +export function view(): IPageHandler { + let editor = useInjectEditor(); + let { state } = editor; + + let { loadClasses, loadDataSetInfo, loadDateSetClassification } = useTool(); + + async function init() { + let { query } = editor.bsState; + if (!query.datasetId || !query.dataId) { + editor.showMsg('error', editor.lang('invalid-query')); + return; + } + + // set mode + editor.setMode(modes.view); + + editor.showLoading(true); + try { + await loadDataSetInfo(); + await Promise.all([loadDateSetClassification(), loadClasses(), loadDataInfo()]); + await editor.loadFrame(0, false); + } catch (error: any) { + editor.handleErr(new BSError('', editor.lang('load-error'), error)); + } + editor.showLoading(false); + } + + async function loadDataInfo() { + let { query } = editor.bsState; + createSingleData(); + } + + function createSingleData() { + let { query } = editor.bsState; + + let dataId = query.dataId; + let data: IFrame = { + id: dataId, + datasetId: query.datasetId, + teamId: '', + loadState: '', + needSave: false, + classifications: [], + dataStatus: 'VALID', + annotationStatus: 'NOT_ANNOTATED', + skipped: false, + }; + + editor.state.frames = [data]; + } + + function onAction(action: IAction) {} + + return { + init, + onAction, + }; +} diff --git a/frontend/text-tool/src/registry.ts b/frontend/text-tool/src/registry.ts new file mode 100644 index 00000000..d8bc5b5d --- /dev/null +++ b/frontend/text-tool/src/registry.ts @@ -0,0 +1,12 @@ +import { Editor } from 'pc-editor'; +import hotkeyConfig from './config/hotkey'; +import * as Actions from './actions'; + +export function initRegistry(editor: Editor) { + // registry kot key + editor.hotkeyManager.registryHotkey(hotkeyConfig); + // registry action + Object.keys(Actions).forEach((name) => { + editor.actionManager.registryAction(name, (Actions as any)[name]); + }); +} diff --git a/frontend/text-tool/src/state.ts b/frontend/text-tool/src/state.ts new file mode 100644 index 00000000..378f0dd0 --- /dev/null +++ b/frontend/text-tool/src/state.ts @@ -0,0 +1,54 @@ +import { provide, inject, reactive } from 'vue'; +import { IBSState } from './type'; +import Editor from './common/Editor'; +import { initRegistry } from './registry'; +import { IState } from 'pc-editor'; + +// global state +export const bsContext = Symbol('pc-tool-editor'); +export const stateContext = Symbol('pc-tool-editor-state'); + +export function useProvideEditor() { + let editor = new Editor(); + // @ts-ignore + window.editor = editor; + + editor.state = reactive(editor.state); + editor.bsState = reactive(editor.bsState); + + initRegistry(editor); + + provide(bsContext, editor); + provide(stateContext, editor.state); + + return editor; +} + +export function useInjectEditor(): Editor { + return inject(bsContext) as Editor; +} + +export function useInjectState(): IState { + return inject(stateContext) as IState; +} + +export function getDefault(): IBSState { + return { + query: {}, + // flow + saving: false, + validing: false, + submitting: false, + modifying: false, + // + // user + user: { + id: '', + nickname: '', + }, + datasetName: '', + datasetType: '', + datasetId: '', + recordId: '', + }; +} diff --git a/frontend/text-tool/src/style/ant.less b/frontend/text-tool/src/style/ant.less new file mode 100644 index 00000000..d2329b3b --- /dev/null +++ b/frontend/text-tool/src/style/ant.less @@ -0,0 +1,67 @@ +.ant-input, +.ant-select-selector { + // background: #47474d !important; + // outline: none !important; + // border: none !important; +} + +.ant-tooltip-inner { + background-color: #333333; +} + +.ant-select, +.ant-select-arrow, +.ant-input, +.ant-radio-wrapper { + color: #cbcbcb; +} +// +.ant-collapse { +} +.ant-collapse > .ant-collapse-item > .ant-collapse-header { + padding: 4px; + padding-left: 40px; + text-align: left; +} + +.ant-collapse-item { + border: none !important; +} + +.ant-collapse-content { + color: white; +} + +.ant-collapse-content-box { + background: #2a2a2c !important; + padding: 0px !important; +} + +.ant-collapse-borderless { + background-color: transparent; +} + +.ant-collapse > .ant-collapse-item { + margin-bottom: 6px; + background: #1e1f23; +} + +.ant-collapse > .ant-collapse-item > .ant-collapse-header { + color: rgb(177 177 177 / 85%); +} + +.ant-dropdown-menu { + background-color: #3a393e; +} + +// ant-slider +.ant-slider-track { + background-color: #177ddc !important; +} +.ant-slider-handle { + border-color: #177ddc !important; +} + +.ant-slider-rail { + background-color: #404040; +} diff --git a/frontend/text-tool/src/style/index.less b/frontend/text-tool/src/style/index.less new file mode 100644 index 00000000..1bbf3816 --- /dev/null +++ b/frontend/text-tool/src/style/index.less @@ -0,0 +1,150 @@ +@import 'ant.less'; + +#app { + font-family: Avenir, Helvetica, Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + // text-align: center; + color: #dee5eb; + margin: 0px; + height: 100%; +} + +body { + margin: 0px; + overflow: hidden; +} + +.limit { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +::-webkit-scrollbar { + width: 4px; +} +::-webkit-scrollbar-track { + background: transparent; + border-radius: 2px; +} +::-webkit-scrollbar-thumb { + background: #4c4c4c; + border-radius: 10px; +} + +.border-bottom { + border-bottom: 1px solid #4a4a4a; +} + +.over-not-allowed { + position: absolute; + inset: 0px; + cursor: not-allowed; +} + +// 2d view +.edit-2d-wrap { + shape-rendering: auto; + .rect-dot { + stroke: #1890ff; + stroke-width: 2; + fill: rgb(255 255 255); + pointer-events: visible; + cursor: pointer; + opacity: 0; + } + .rect-line { + pointer-events: visible; + opacity: 0; + } + circle.rect-lt { + cursor: nw-resize; + } + circle.rect-lb { + cursor: sw-resize; + } + circle.rect-rt { + cursor: ne-resize; + } + circle.rect-rb { + cursor: se-resize; + } + .rect-right, + .rect-left { + cursor: col-resize; + } + .rect-rotate { + fill: rgba(204, 204, 204, 0.63); + stroke: rgba(204, 204, 204, 0.63); + cursor: grabbing; + pointer-events: visible; + } + .rect-top, + .rect-bottom { + cursor: row-resize; + } + + .rect-bg, + .rect-back-bg, + .rect-front-bg { + fill: rgb(250 173 20 / 0%); + cursor: move; + pointer-events: visible; + } + + .rect-bg { + stroke: red; + stroke-width: 1; + } + + .wrap-8 { + .rect-back-bg:hover, + .rect-front-bg:hover { + fill: rgb(250 173 20 / 24%); + } + } + + .wrap-8 { + .rect-back-bg, + .rect-front-bg { + stroke-dasharray: 5; + stroke: red; + stroke-width: 1; + } + } +} +.vc-colorpicker { + width: 200px !important; + .vc-colorpicker--container { + padding: 4px; + .vc-saturation { + height: 100px; + } + .vc-chrome-colorPicker > .vc-chrome-colorPicker-body { + .vc-display { + margin: 0; + } + .chrome-controls > .chrome-color-wrap { + display: none; + } + } + } +} + +@keyframes loading-360 { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.loading { + animation: loading-360 0.8s infinite linear; +} + +.no-info { + color: #ccc; +} diff --git a/frontend/text-tool/src/type.ts b/frontend/text-tool/src/type.ts new file mode 100644 index 00000000..0e5d6c0f --- /dev/null +++ b/frontend/text-tool/src/type.ts @@ -0,0 +1,36 @@ +export interface IUser { + id: string; + nickname: string; + email?: string; + status?: string; + username?: string; +} + +export interface IBSState { + query: Record; + // flow + saving: boolean; + validing: boolean; + submitting: boolean; + modifying: boolean; + recordId: string; + // dataset info + datasetId: string; + datasetName: string; + datasetType: string; + seriesFrameId?: string; + // + user: IUser; +} + +export type IAction = 'save' | 'close'; + +export interface IOption { + label: string; + value: string; +} + +export interface IPageHandler { + init: () => void; + onAction: (e: IAction) => void; +} diff --git a/frontend/text-tool/src/utils/common.ts b/frontend/text-tool/src/utils/common.ts new file mode 100644 index 00000000..0133eb4c --- /dev/null +++ b/frontend/text-tool/src/utils/common.ts @@ -0,0 +1,18 @@ +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +dayjs.extend(utc); +export function formatNumber(str: string | number, precision: number = 2): string { + str = '' + str; + let regex = /(?!^)(?=(\d{3})+(\.|$))/g; + str.replace(regex, ','); + + if (precision) { + return (+str).toFixed(precision); + } else { + return str; + } +} +export function formatTimeUTC(time: number) { + // return dayjs(time).utc().format('YYYY-MM-DDTHH:mm:ss[Z]'); + return dayjs(time).utc().format(); +} diff --git a/frontend/text-tool/src/utils/index.ts b/frontend/text-tool/src/utils/index.ts new file mode 100644 index 00000000..f701b53a --- /dev/null +++ b/frontend/text-tool/src/utils/index.ts @@ -0,0 +1,2 @@ +export * from './common'; +export * from './model'; diff --git a/frontend/text-tool/src/utils/model.ts b/frontend/text-tool/src/utils/model.ts new file mode 100644 index 00000000..02a518a1 --- /dev/null +++ b/frontend/text-tool/src/utils/model.ts @@ -0,0 +1,63 @@ +import * as api from '../api'; +// import Editor from '../common/Editor'; +import { IObject } from 'pc-editor'; + +export function pollModelTrack( + recordId: string, + onComplete: (e: Record) => void, + onErr?: () => void, +) { + let stop = false; + let hasErr = false; + poll(); + return clear; + + async function poll() { + let result; + + try { + result = await request(); + } catch (error: any) { + hasErr = true; + } + + if (stop) return; + + if (hasErr) { + onErr && onErr(); + } else { + if (result) onComplete(result); + else { + setTimeout(poll, 1000); + } + } + } + + function clear() { + stop = true; + } + + async function request() { + let request = api.getModelResult([], recordId).then((data) => { + data = data.data || {}; + let resultList = data.modelDataResults || []; + if (resultList.length === 0) return; + + let dataResult = resultList[0]; + // let dataId = dataResult.dataId as string; + let objectsMap = {} as Record; + dataResult.modelResult.forEach((objects: any[], index: number) => { + if (objects.length === 0) return; + + objectsMap[index] = []; + objects.forEach((e) => { + e.trackId = e.trackingId; + }); + objectsMap[index] = objects; + }); + + return objectsMap; + }); + return request; + } +} diff --git a/frontend/text-tool/tsconfig.json b/frontend/text-tool/tsconfig.json new file mode 100644 index 00000000..c98576f8 --- /dev/null +++ b/frontend/text-tool/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "suppressImplicitAnyIndexErrors": true, + "module": "esnext", + "moduleResolution": "node", + "strict": true, + "jsx": "preserve", + "baseUrl": ".", + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "lib": ["esnext", "dom"], + "paths": { + "pc-render": ["src/packages/pc-render"], + "pc-editor": ["src/packages/pc-editor"], + } + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/frontend/text-tool/tsconfig.node.json b/frontend/text-tool/tsconfig.node.json new file mode 100644 index 00000000..e993792c --- /dev/null +++ b/frontend/text-tool/tsconfig.node.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "composite": true, + "module": "esnext", + "moduleResolution": "node" + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/text-tool/vite.config.build.ts b/frontend/text-tool/vite.config.build.ts new file mode 100644 index 00000000..0949d9ad --- /dev/null +++ b/frontend/text-tool/vite.config.build.ts @@ -0,0 +1,10 @@ +const { defineConfig, mergeConfig } = require('vite'); +const baseConfig = require('./vite.config'); + +// https://vitejs.dev/config/ +module.exports = mergeConfig(baseConfig, { + base: '/tool/pc/', + build: { + outDir: '../dist/pc-tool', + }, +}); diff --git a/frontend/text-tool/vite.config.ts b/frontend/text-tool/vite.config.ts new file mode 100644 index 00000000..97e7306d --- /dev/null +++ b/frontend/text-tool/vite.config.ts @@ -0,0 +1,38 @@ +const { defineConfig, mergeConfig } = require('vite'); +const vue = require('@vitejs/plugin-vue'); +const path = require('path'); +const fs = require('fs'); + +let localConfig = getLocalConfig(); +// https://vitejs.dev/config/ +const config = defineConfig({ + server: { + open: true, + port: 3200, + // api proxy when development + proxy: { + '/api': { + changeOrigin: true, + target: 'https://xtreme1.alidev.beisai.com', + }, + }, + }, + plugins: [vue()], + alias: [ + { find: 'pc-render', replacement: path.resolve(__dirname, './src/packages/pc-render') }, + { find: 'pc-editor', replacement: path.resolve(__dirname, './src/packages/pc-editor') }, + ], +}); + +module.exports = mergeConfig(config, localConfig); + +function getLocalConfig() { + let file = path.resolve(__dirname, './vite.config.local.js'); + let config = {}; + if (fs.existsSync(file)) { + try { + config = require(file); + } catch (e) {} + } + return config; +} diff --git a/frontend/text-tool/yarn.lock b/frontend/text-tool/yarn.lock new file mode 100644 index 00000000..d328a341 --- /dev/null +++ b/frontend/text-tool/yarn.lock @@ -0,0 +1,5117 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@aesoper/normal-utils@^0.1.5": + version "0.1.5" + resolved "https://registry.npmmirror.com/@aesoper/normal-utils/-/normal-utils-0.1.5.tgz#82b7c899ab9670c55515f949a3766d24260b8039" + integrity sha512-LFF/6y6h5mfwhnJaWqqxuC8zzDaHCG62kMRkd8xhDtq62TQj9dM17A9DhE87W7DhiARJsHLgcina/9P4eNCN1w== + +"@ant-design/colors@^6.0.0": + version "6.0.0" + resolved "https://registry.npmmirror.com/@ant-design/colors/-/colors-6.0.0.tgz#9b9366257cffcc47db42b9d0203bb592c13c0298" + integrity sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ== + dependencies: + "@ctrl/tinycolor" "^3.4.0" + +"@ant-design/icons-svg@^4.2.1": + version "4.2.1" + resolved "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.2.1.tgz#8630da8eb4471a4aabdaed7d1ff6a97dcb2cf05a" + integrity sha512-EB0iwlKDGpG93hW8f85CTJTs4SvMX7tt5ceupvhALp1IF44SeUFOMhKUOYqpsoYWQKAOuTRDMqn75rEaKDp0Xw== + +"@ant-design/icons-vue@^6.0.0", "@ant-design/icons-vue@^6.0.1": + version "6.1.0" + resolved "https://registry.npmmirror.com/@ant-design/icons-vue/-/icons-vue-6.1.0.tgz#f9324fdc0eb4cea943cf626d2bf3db9a4ff4c074" + integrity sha512-EX6bYm56V+ZrKN7+3MT/ubDkvJ5rK/O2t380WFRflDcVFgsvl3NLH7Wxeau6R8DbrO5jWR6DSTC3B6gYFp77AA== + dependencies: + "@ant-design/colors" "^6.0.0" + "@ant-design/icons-svg" "^4.2.1" + +"@babel/code-frame@^7.0.0": + version "7.21.4" + resolved "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39" + integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g== + dependencies: + "@babel/highlight" "^7.18.6" + +"@babel/helper-string-parser@^7.19.4": + version "7.19.4" + resolved "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" + integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== + +"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": + version "7.19.1" + resolved "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== + +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.npmmirror.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.16.4", "@babel/parser@^7.6.0", "@babel/parser@^7.9.6": + version "7.21.4" + resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.21.4.tgz#94003fdfc520bbe2875d4ae557b43ddb6d880f17" + integrity sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw== + +"@babel/runtime@^7.10.5": + version "7.21.0" + resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" + integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== + dependencies: + regenerator-runtime "^0.13.11" + +"@babel/types@^7.6.1", "@babel/types@^7.9.6": + version "7.21.4" + resolved "https://registry.npmmirror.com/@babel/types/-/types-7.21.4.tgz#2d5d6bb7908699b3b416409ffd3b5daa25b030d4" + integrity sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA== + dependencies: + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.npmmirror.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + +"@commitlint/cli@^16.2.3": + version "16.3.0" + resolved "https://registry.npmmirror.com/@commitlint/cli/-/cli-16.3.0.tgz#5689f5c2abbb7880d5ff13329251e5648a784b16" + integrity sha512-P+kvONlfsuTMnxSwWE1H+ZcPMY3STFaHb2kAacsqoIkNx66O0T7sTpBxpxkMrFPyhkJiLJnJWMhk4bbvYD3BMA== + dependencies: + "@commitlint/format" "^16.2.1" + "@commitlint/lint" "^16.2.4" + "@commitlint/load" "^16.3.0" + "@commitlint/read" "^16.2.1" + "@commitlint/types" "^16.2.1" + lodash "^4.17.19" + resolve-from "5.0.0" + resolve-global "1.0.0" + yargs "^17.0.0" + +"@commitlint/config-conventional@^16.2.1": + version "16.2.4" + resolved "https://registry.npmmirror.com/@commitlint/config-conventional/-/config-conventional-16.2.4.tgz#56647108c89ed06fc5271242787550331988c0fb" + integrity sha512-av2UQJa3CuE5P0dzxj/o/B9XVALqYzEViHrMXtDrW9iuflrqCStWBAioijppj9URyz6ONpohJKAtSdgAOE0gkA== + dependencies: + conventional-changelog-conventionalcommits "^4.3.1" + +"@commitlint/config-validator@^16.2.1": + version "16.2.1" + resolved "https://registry.npmmirror.com/@commitlint/config-validator/-/config-validator-16.2.1.tgz#794e769afd4756e4cf1bfd823b6612932e39c56d" + integrity sha512-hogSe0WGg7CKmp4IfNbdNES3Rq3UEI4XRPB8JL4EPgo/ORq5nrGTVzxJh78omibNuB8Ho4501Czb1Er1MoDWpw== + dependencies: + "@commitlint/types" "^16.2.1" + ajv "^6.12.6" + +"@commitlint/ensure@^16.2.1": + version "16.2.1" + resolved "https://registry.npmmirror.com/@commitlint/ensure/-/ensure-16.2.1.tgz#0fc538173f95c1eb2694eeedb79cab478347f16f" + integrity sha512-/h+lBTgf1r5fhbDNHOViLuej38i3rZqTQnBTk+xEg+ehOwQDXUuissQ5GsYXXqI5uGy+261ew++sT4EA3uBJ+A== + dependencies: + "@commitlint/types" "^16.2.1" + lodash "^4.17.19" + +"@commitlint/execute-rule@^16.2.1": + version "16.2.1" + resolved "https://registry.npmmirror.com/@commitlint/execute-rule/-/execute-rule-16.2.1.tgz#60be73be4b9af97a41546e7ce59fdd33787c65f8" + integrity sha512-oSls82fmUTLM6cl5V3epdVo4gHhbmBFvCvQGHBRdQ50H/690Uq1Dyd7hXMuKITCIdcnr9umyDkr8r5C6HZDF3g== + +"@commitlint/format@^16.2.1": + version "16.2.1" + resolved "https://registry.npmmirror.com/@commitlint/format/-/format-16.2.1.tgz#6e673f710c799be78e68b2682323e04f75080d07" + integrity sha512-Yyio9bdHWmNDRlEJrxHKglamIk3d6hC0NkEUW6Ti6ipEh2g0BAhy8Od6t4vLhdZRa1I2n+gY13foy+tUgk0i1Q== + dependencies: + "@commitlint/types" "^16.2.1" + chalk "^4.0.0" + +"@commitlint/is-ignored@^16.2.4": + version "16.2.4" + resolved "https://registry.npmmirror.com/@commitlint/is-ignored/-/is-ignored-16.2.4.tgz#369e40a240ad5451bf2b57a80829253129d7f19b" + integrity sha512-Lxdq9aOAYCOOOjKi58ulbwK/oBiiKz+7Sq0+/SpFIEFwhHkIVugvDvWjh2VRBXmRC/x5lNcjDcYEwS/uYUvlYQ== + dependencies: + "@commitlint/types" "^16.2.1" + semver "7.3.7" + +"@commitlint/lint@^16.2.4": + version "16.2.4" + resolved "https://registry.npmmirror.com/@commitlint/lint/-/lint-16.2.4.tgz#575f5a9d227dddfca8386253d9aff27be5b94788" + integrity sha512-AUDuwOxb2eGqsXbTMON3imUGkc1jRdtXrbbohiLSCSk3jFVXgJLTMaEcr39pR00N8nE9uZ+V2sYaiILByZVmxQ== + dependencies: + "@commitlint/is-ignored" "^16.2.4" + "@commitlint/parse" "^16.2.1" + "@commitlint/rules" "^16.2.4" + "@commitlint/types" "^16.2.1" + +"@commitlint/load@^16.3.0": + version "16.3.0" + resolved "https://registry.npmmirror.com/@commitlint/load/-/load-16.3.0.tgz#e674ccc9edefd64a2d8b82d175de81ec3bb70eca" + integrity sha512-3tykjV/iwbkv2FU9DG+NZ/JqmP0Nm3b7aDwgCNQhhKV5P74JAuByULkafnhn+zsFGypG1qMtI5u+BZoa9APm0A== + dependencies: + "@commitlint/config-validator" "^16.2.1" + "@commitlint/execute-rule" "^16.2.1" + "@commitlint/resolve-extends" "^16.2.1" + "@commitlint/types" "^16.2.1" + "@types/node" ">=12" + chalk "^4.0.0" + cosmiconfig "^7.0.0" + cosmiconfig-typescript-loader "^2.0.0" + lodash "^4.17.19" + resolve-from "^5.0.0" + typescript "^4.4.3" + +"@commitlint/message@^16.2.1": + version "16.2.1" + resolved "https://registry.npmmirror.com/@commitlint/message/-/message-16.2.1.tgz#bc6a0fa446a746ac2ca78cf372e4cec48daf620d" + integrity sha512-2eWX/47rftViYg7a3axYDdrgwKv32mxbycBJT6OQY/MJM7SUfYNYYvbMFOQFaA4xIVZt7t2Alyqslbl6blVwWw== + +"@commitlint/parse@^16.2.1": + version "16.2.1" + resolved "https://registry.npmmirror.com/@commitlint/parse/-/parse-16.2.1.tgz#50b359cb711ec566d2ee236a8e4c6baca07b77c0" + integrity sha512-2NP2dDQNL378VZYioLrgGVZhWdnJO4nAxQl5LXwYb08nEcN+cgxHN1dJV8OLJ5uxlGJtDeR8UZZ1mnQ1gSAD/g== + dependencies: + "@commitlint/types" "^16.2.1" + conventional-changelog-angular "^5.0.11" + conventional-commits-parser "^3.2.2" + +"@commitlint/read@^16.2.1": + version "16.2.1" + resolved "https://registry.npmmirror.com/@commitlint/read/-/read-16.2.1.tgz#e0539205d77cdb6879b560f95e5fb251e0c6f562" + integrity sha512-tViXGuaxLTrw2r7PiYMQOFA2fueZxnnt0lkOWqKyxT+n2XdEMGYcI9ID5ndJKXnfPGPppD0w/IItKsIXlZ+alw== + dependencies: + "@commitlint/top-level" "^16.2.1" + "@commitlint/types" "^16.2.1" + fs-extra "^10.0.0" + git-raw-commits "^2.0.0" + +"@commitlint/resolve-extends@^16.2.1": + version "16.2.1" + resolved "https://registry.npmmirror.com/@commitlint/resolve-extends/-/resolve-extends-16.2.1.tgz#2f7833a5a3a7aa79f508e59fcb0f1d33c45ed360" + integrity sha512-NbbCMPKTFf2J805kwfP9EO+vV+XvnaHRcBy6ud5dF35dxMsvdJqke54W3XazXF1ZAxC4a3LBy4i/GNVBAthsEg== + dependencies: + "@commitlint/config-validator" "^16.2.1" + "@commitlint/types" "^16.2.1" + import-fresh "^3.0.0" + lodash "^4.17.19" + resolve-from "^5.0.0" + resolve-global "^1.0.0" + +"@commitlint/rules@^16.2.4": + version "16.2.4" + resolved "https://registry.npmmirror.com/@commitlint/rules/-/rules-16.2.4.tgz#c2fbbf20d9d0e8fcf25690c88a27750d4a3e867b" + integrity sha512-rK5rNBIN2ZQNQK+I6trRPK3dWa0MtaTN4xnwOma1qxa4d5wQMQJtScwTZjTJeallFxhOgbNOgr48AMHkdounVg== + dependencies: + "@commitlint/ensure" "^16.2.1" + "@commitlint/message" "^16.2.1" + "@commitlint/to-lines" "^16.2.1" + "@commitlint/types" "^16.2.1" + execa "^5.0.0" + +"@commitlint/to-lines@^16.2.1": + version "16.2.1" + resolved "https://registry.npmmirror.com/@commitlint/to-lines/-/to-lines-16.2.1.tgz#42d000f34dc0406f514991e86237fdab5e8affd0" + integrity sha512-9/VjpYj5j1QeY3eiog1zQWY6axsdWAc0AonUUfyZ7B0MVcRI0R56YsHAfzF6uK/g/WwPZaoe4Lb1QCyDVnpVaQ== + +"@commitlint/top-level@^16.2.1": + version "16.2.1" + resolved "https://registry.npmmirror.com/@commitlint/top-level/-/top-level-16.2.1.tgz#bdaa53ab3d8970e0288879f1a342a8c2dfe01583" + integrity sha512-lS6GSieHW9y6ePL73ied71Z9bOKyK+Ib9hTkRsB8oZFAyQZcyRwq2w6nIa6Fngir1QW51oKzzaXfJL94qwImyw== + dependencies: + find-up "^5.0.0" + +"@commitlint/types@^16.2.1": + version "16.2.1" + resolved "https://registry.npmmirror.com/@commitlint/types/-/types-16.2.1.tgz#f25d373b88b01e51fc3fa44488101361945a61bd" + integrity sha512-7/z7pA7BM0i8XvMSBynO7xsB3mVQPUZbVn6zMIlp/a091XJ3qAXRXc+HwLYhiIdzzS5fuxxNIHZMGHVD4HJxdA== + dependencies: + chalk "^4.0.0" + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.npmmirror.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@ctrl/tinycolor@^3.4.0": + version "3.6.0" + resolved "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.0.tgz#53fa5fe9c34faee89469e48f91d51a3766108bc8" + integrity sha512-/Z3l6pXthq0JvMYdUFyX9j0MaCltlIn6mfh9jLyQwg5aPKxkyNa0PTHtU1AlFXLNk55ZuAeJRcpvq+tmLfKmaQ== + +"@emmetio/abbreviation@^2.3.1": + version "2.3.1" + resolved "https://registry.npmmirror.com/@emmetio/abbreviation/-/abbreviation-2.3.1.tgz#b5ec06b9629ec63292f3a378e716dcb3071924ea" + integrity sha512-QXgYlXZGprqb6aCBJPPWVBN/Jb69khJF73GGJkOk//PoMgSbPGuaHn1hCRolctnzlBHjCIC6Om97Pw46/1A23g== + dependencies: + "@emmetio/scanner" "^1.0.2" + +"@emmetio/css-abbreviation@^2.1.6": + version "2.1.6" + resolved "https://registry.npmmirror.com/@emmetio/css-abbreviation/-/css-abbreviation-2.1.6.tgz#f37f7ffb8d76efc40d3629739758531bbe810d7b" + integrity sha512-bvuPogt0OvwcILRg+ZD/oej1H72xwOhUDPWOmhCWLJrZZ8bMTazsWnvw8a8noaaVqUhOE9PsC0tYgGVv5N7fsw== + dependencies: + "@emmetio/scanner" "^1.0.2" + +"@emmetio/scanner@^1.0.2": + version "1.0.2" + resolved "https://registry.npmmirror.com/@emmetio/scanner/-/scanner-1.0.2.tgz#ee96ecb9f9cfa5f7a876a13011ae74ee06e9100d" + integrity sha512-1ESCGgXRgn1r29hRmz8K0G4Ywr5jDWezMgRnICComBCWmg3znLWU8+tmakuM1og1Vn4W/sauvlABl/oq2pve8w== + +"@esbuild/linux-loong64@0.14.54": + version "0.14.54" + resolved "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028" + integrity sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw== + +"@eslint-community/eslint-utils@^4.2.0": + version "4.4.0" + resolved "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.4.0": + version "4.5.0" + resolved "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.5.0.tgz#f6f729b02feee2c749f57e334b7a1b5f40a81724" + integrity sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ== + +"@eslint/eslintrc@^2.0.2": + version "2.0.2" + resolved "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-2.0.2.tgz#01575e38707add677cf73ca1589abba8da899a02" + integrity sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.5.1" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.39.0": + version "8.39.0" + resolved "https://registry.npmmirror.com/@eslint/js/-/js-8.39.0.tgz#58b536bcc843f4cd1e02a7e6171da5c040f4d44b" + integrity sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng== + +"@gar/promisify@^1.1.3": + version "1.1.3" + resolved "https://registry.npmmirror.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" + integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== + +"@humanwhocodes/config-array@^0.11.8": + version "0.11.8" + resolved "https://registry.npmmirror.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9" + integrity sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g== + dependencies: + "@humanwhocodes/object-schema" "^1.2.1" + debug "^4.1.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.npmmirror.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +"@interactjs/types@1.10.17": + version "1.10.17" + resolved "https://registry.npmmirror.com/@interactjs/types/-/types-1.10.17.tgz#1649de06d9ead790c81ecece76736b852bdfc77e" + integrity sha512-X2JpoM7xUw0p9Me0tMaI0HNfcF/Hd07ZZlzpnpEMpGerUZOLoyeThrV9P+CrBHxZrluWJrigJbcdqXliFd0YMA== + +"@intlify/core-base@9.2.2": + version "9.2.2" + resolved "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.2.2.tgz#5353369b05cc9fe35cab95fe20afeb8a4481f939" + integrity sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA== + dependencies: + "@intlify/devtools-if" "9.2.2" + "@intlify/message-compiler" "9.2.2" + "@intlify/shared" "9.2.2" + "@intlify/vue-devtools" "9.2.2" + +"@intlify/devtools-if@9.2.2": + version "9.2.2" + resolved "https://registry.npmmirror.com/@intlify/devtools-if/-/devtools-if-9.2.2.tgz#b13d9ac4b4e2fe6d2e7daa556517a8061fe8bd39" + integrity sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg== + dependencies: + "@intlify/shared" "9.2.2" + +"@intlify/message-compiler@9.2.2": + version "9.2.2" + resolved "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.2.2.tgz#e42ab6939b8ae5b3d21faf6a44045667a18bba1c" + integrity sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA== + dependencies: + "@intlify/shared" "9.2.2" + source-map "0.6.1" + +"@intlify/shared@9.2.2": + version "9.2.2" + resolved "https://registry.npmmirror.com/@intlify/shared/-/shared-9.2.2.tgz#5011be9ca2b4ab86f8660739286e2707f9abb4a5" + integrity sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q== + +"@intlify/vue-devtools@9.2.2": + version "9.2.2" + resolved "https://registry.npmmirror.com/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz#b95701556daf7ebb3a2d45aa3ae9e6415aed8317" + integrity sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg== + dependencies: + "@intlify/core-base" "9.2.2" + "@intlify/shared" "9.2.2" + +"@isaacs/string-locale-compare@^1.1.0": + version "1.1.0" + resolved "https://registry.npmmirror.com/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz#291c227e93fd407a96ecd59879a35809120e432b" + integrity sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ== + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.1" + resolved "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.15" + resolved "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@npmcli/arborist@^5.6.3": + version "5.6.3" + resolved "https://registry.npmmirror.com/@npmcli/arborist/-/arborist-5.6.3.tgz#40810080272e097b4a7a4f56108f4a31638a9874" + integrity sha512-/7hbqEM6YuRjwTcQXkK1+xKslEblY5kFQe0tZ7jKyMlIR6x4iOmhLErIkBBGtTKvYxRKdpcxnFXjCobg3UqmsA== + dependencies: + "@isaacs/string-locale-compare" "^1.1.0" + "@npmcli/installed-package-contents" "^1.0.7" + "@npmcli/map-workspaces" "^2.0.3" + "@npmcli/metavuln-calculator" "^3.0.1" + "@npmcli/move-file" "^2.0.0" + "@npmcli/name-from-folder" "^1.0.1" + "@npmcli/node-gyp" "^2.0.0" + "@npmcli/package-json" "^2.0.0" + "@npmcli/query" "^1.2.0" + "@npmcli/run-script" "^4.1.3" + bin-links "^3.0.3" + cacache "^16.1.3" + common-ancestor-path "^1.0.1" + hosted-git-info "^5.2.1" + json-parse-even-better-errors "^2.3.1" + json-stringify-nice "^1.1.4" + minimatch "^5.1.0" + mkdirp "^1.0.4" + mkdirp-infer-owner "^2.0.0" + nopt "^6.0.0" + npm-install-checks "^5.0.0" + npm-package-arg "^9.0.0" + npm-pick-manifest "^7.0.2" + npm-registry-fetch "^13.0.0" + npmlog "^6.0.2" + pacote "^13.6.1" + parse-conflict-json "^2.0.1" + proc-log "^2.0.0" + promise-all-reject-late "^1.0.0" + promise-call-limit "^1.0.1" + read-package-json-fast "^2.0.2" + readdir-scoped-modules "^1.1.0" + rimraf "^3.0.2" + semver "^7.3.7" + ssri "^9.0.0" + treeverse "^2.0.0" + walk-up-path "^1.0.0" + +"@npmcli/ci-detect@^2.0.0": + version "2.0.0" + resolved "https://registry.npmmirror.com/@npmcli/ci-detect/-/ci-detect-2.0.0.tgz#e63c91bcd4185ac1e85720a34fc48e164ece5b89" + integrity sha512-8yQtQ9ArHh/TzdUDKQwEvwCgpDuhSWTDAbiKMl3854PcT+Dk4UmWaiawuFTLy9n5twzXOBXVflWe+90/ffXQrA== + +"@npmcli/config@^4.2.1": + version "4.2.2" + resolved "https://registry.npmmirror.com/@npmcli/config/-/config-4.2.2.tgz#2e3334dda84f48d059309c53d152e66b05ca24b7" + integrity sha512-5GNcLd+0c4bYBnFop53+26CO5GQP0R9YcxlernohpHDWdIgzUg9I0+GEMk3sNHnLntATVU39d283A4OO+W402w== + dependencies: + "@npmcli/map-workspaces" "^2.0.2" + ini "^3.0.0" + mkdirp-infer-owner "^2.0.0" + nopt "^6.0.0" + proc-log "^2.0.0" + read-package-json-fast "^2.0.3" + semver "^7.3.5" + walk-up-path "^1.0.0" + +"@npmcli/disparity-colors@^2.0.0": + version "2.0.0" + resolved "https://registry.npmmirror.com/@npmcli/disparity-colors/-/disparity-colors-2.0.0.tgz#cb518166ee21573b96241a3613fef70acb2a60ba" + integrity sha512-FFXGrIjhvd2qSZ8iS0yDvbI7nbjdyT2VNO7wotosjYZM2p2r8PN3B7Om3M5NO9KqW/OVzfzLB3L0V5Vo5QXC7A== + dependencies: + ansi-styles "^4.3.0" + +"@npmcli/fs@^2.1.0", "@npmcli/fs@^2.1.1": + version "2.1.2" + resolved "https://registry.npmmirror.com/@npmcli/fs/-/fs-2.1.2.tgz#a9e2541a4a2fec2e69c29b35e6060973da79b865" + integrity sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ== + dependencies: + "@gar/promisify" "^1.1.3" + semver "^7.3.5" + +"@npmcli/git@^3.0.0": + version "3.0.2" + resolved "https://registry.npmmirror.com/@npmcli/git/-/git-3.0.2.tgz#5c5de6b4d70474cf2d09af149ce42e4e1dacb931" + integrity sha512-CAcd08y3DWBJqJDpfuVL0uijlq5oaXaOJEKHKc4wqrjd00gkvTZB+nFuLn+doOOKddaQS9JfqtNoFCO2LCvA3w== + dependencies: + "@npmcli/promise-spawn" "^3.0.0" + lru-cache "^7.4.4" + mkdirp "^1.0.4" + npm-pick-manifest "^7.0.0" + proc-log "^2.0.0" + promise-inflight "^1.0.1" + promise-retry "^2.0.1" + semver "^7.3.5" + which "^2.0.2" + +"@npmcli/installed-package-contents@^1.0.7": + version "1.0.7" + resolved "https://registry.npmmirror.com/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz#ab7408c6147911b970a8abe261ce512232a3f4fa" + integrity sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw== + dependencies: + npm-bundled "^1.1.1" + npm-normalize-package-bin "^1.0.1" + +"@npmcli/map-workspaces@^2.0.2", "@npmcli/map-workspaces@^2.0.3": + version "2.0.4" + resolved "https://registry.npmmirror.com/@npmcli/map-workspaces/-/map-workspaces-2.0.4.tgz#9e5e8ab655215a262aefabf139782b894e0504fc" + integrity sha512-bMo0aAfwhVwqoVM5UzX1DJnlvVvzDCHae821jv48L1EsrYwfOZChlqWYXEtto/+BkBXetPbEWgau++/brh4oVg== + dependencies: + "@npmcli/name-from-folder" "^1.0.1" + glob "^8.0.1" + minimatch "^5.0.1" + read-package-json-fast "^2.0.3" + +"@npmcli/metavuln-calculator@^3.0.1": + version "3.1.1" + resolved "https://registry.npmmirror.com/@npmcli/metavuln-calculator/-/metavuln-calculator-3.1.1.tgz#9359bd72b400f8353f6a28a25c8457b562602622" + integrity sha512-n69ygIaqAedecLeVH3KnO39M6ZHiJ2dEv5A7DGvcqCB8q17BGUgW8QaanIkbWUo2aYGZqJaOORTLAlIvKjNDKA== + dependencies: + cacache "^16.0.0" + json-parse-even-better-errors "^2.3.1" + pacote "^13.0.3" + semver "^7.3.5" + +"@npmcli/move-file@^2.0.0": + version "2.0.1" + resolved "https://registry.npmmirror.com/@npmcli/move-file/-/move-file-2.0.1.tgz#26f6bdc379d87f75e55739bab89db525b06100e4" + integrity sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + +"@npmcli/name-from-folder@^1.0.1": + version "1.0.1" + resolved "https://registry.npmmirror.com/@npmcli/name-from-folder/-/name-from-folder-1.0.1.tgz#77ecd0a4fcb772ba6fe927e2e2e155fbec2e6b1a" + integrity sha512-qq3oEfcLFwNfEYOQ8HLimRGKlD8WSeGEdtUa7hmzpR8Sa7haL1KVQrvgO6wqMjhWFFVjgtrh1gIxDz+P8sjUaA== + +"@npmcli/node-gyp@^2.0.0": + version "2.0.0" + resolved "https://registry.npmmirror.com/@npmcli/node-gyp/-/node-gyp-2.0.0.tgz#8c20e53e34e9078d18815c1d2dda6f2420d75e35" + integrity sha512-doNI35wIe3bBaEgrlPfdJPaCpUR89pJWep4Hq3aRdh6gKazIVWfs0jHttvSSoq47ZXgC7h73kDsUl8AoIQUB+A== + +"@npmcli/package-json@^2.0.0": + version "2.0.0" + resolved "https://registry.npmmirror.com/@npmcli/package-json/-/package-json-2.0.0.tgz#3bbcf4677e21055adbe673d9f08c9f9cde942e4a" + integrity sha512-42jnZ6yl16GzjWSH7vtrmWyJDGVa/LXPdpN2rcUWolFjc9ON2N3uz0qdBbQACfmhuJZ2lbKYtmK5qx68ZPLHMA== + dependencies: + json-parse-even-better-errors "^2.3.1" + +"@npmcli/promise-spawn@^3.0.0": + version "3.0.0" + resolved "https://registry.npmmirror.com/@npmcli/promise-spawn/-/promise-spawn-3.0.0.tgz#53283b5f18f855c6925f23c24e67c911501ef573" + integrity sha512-s9SgS+p3a9Eohe68cSI3fi+hpcZUmXq5P7w0kMlAsWVtR7XbK3ptkZqKT2cK1zLDObJ3sR+8P59sJE0w/KTL1g== + dependencies: + infer-owner "^1.0.4" + +"@npmcli/query@^1.2.0": + version "1.2.0" + resolved "https://registry.npmmirror.com/@npmcli/query/-/query-1.2.0.tgz#46468d583cf013aa92102970700f9555314aabe4" + integrity sha512-uWglsUM3PjBLgTSmZ3/vygeGdvWEIZ3wTUnzGFbprC/RtvQSaT+GAXu1DXmSFj2bD3oOZdcRm1xdzsV2z1YWdw== + dependencies: + npm-package-arg "^9.1.0" + postcss-selector-parser "^6.0.10" + semver "^7.3.7" + +"@npmcli/run-script@^4.1.0", "@npmcli/run-script@^4.1.3", "@npmcli/run-script@^4.2.0", "@npmcli/run-script@^4.2.1": + version "4.2.1" + resolved "https://registry.npmmirror.com/@npmcli/run-script/-/run-script-4.2.1.tgz#c07c5c71bc1c70a5f2a06b0d4da976641609b946" + integrity sha512-7dqywvVudPSrRCW5nTHpHgeWnbBtz8cFkOuKrecm6ih+oO9ciydhWt6OF7HlqupRRmB8Q/gECVdB9LMfToJbRg== + dependencies: + "@npmcli/node-gyp" "^2.0.0" + "@npmcli/promise-spawn" "^3.0.0" + node-gyp "^9.0.0" + read-package-json-fast "^2.0.3" + which "^2.0.2" + +"@popperjs/core@^2.10.1": + version "2.11.7" + resolved "https://registry.npmmirror.com/@popperjs/core/-/core-2.11.7.tgz#ccab5c8f7dc557a52ca3288c10075c9ccd37fff7" + integrity sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw== + +"@simonwep/pickr@~1.8.0": + version "1.8.2" + resolved "https://registry.npmmirror.com/@simonwep/pickr/-/pickr-1.8.2.tgz#96dc86675940d7cad63d69c22083dd1cbb9797cb" + integrity sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA== + dependencies: + core-js "^3.15.1" + nanopop "^2.1.0" + +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.npmmirror.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== + +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.npmmirror.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.npmmirror.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.npmmirror.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.3" + resolved "https://registry.npmmirror.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" + integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== + +"@tweenjs/tween.js@^18.6.4": + version "18.6.4" + resolved "https://registry.npmmirror.com/@tweenjs/tween.js/-/tween.js-18.6.4.tgz#40a3d0a93647124872dec8e0fd1bd5926695b6ca" + integrity sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ== + +"@types/axios@^0.14.0": + version "0.14.0" + resolved "https://registry.npmmirror.com/@types/axios/-/axios-0.14.0.tgz#ec2300fbe7d7dddd7eb9d3abf87999964cafce46" + integrity sha512-KqQnQbdYE54D7oa/UmYVMZKq7CO4l8DEENzOKc4aBRwxCXSlJXGz83flFx5L7AWrOQnmuN3kVsRdt+GZPPjiVQ== + dependencies: + axios "*" + +"@types/js-cookie@^3.0.1": + version "3.0.3" + resolved "https://registry.npmmirror.com/@types/js-cookie/-/js-cookie-3.0.3.tgz#d6bfbbdd0c187354ca555213d1962f6d0691ff4e" + integrity sha512-Xe7IImK09HP1sv2M/aI+48a20VX+TdRJucfq4vfRVy6nWN8PYPOEnlMRSgxJAgYQIXJVL8dZ4/ilAM7dWNaOww== + +"@types/json-schema@^7.0.9": + version "7.0.11" + resolved "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== + +"@types/lodash@^4.14.180": + version "4.14.194" + resolved "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.194.tgz#b71eb6f7a0ff11bff59fc987134a093029258a76" + integrity sha512-r22s9tAS7imvBt2lyHC9B8AGwWnXaYb1tY09oyLkXDs4vArpYJzw09nj8MLx5VfciBPGIb+ZwG0ssYnEPJxn/g== + +"@types/minimist@^1.2.0": + version "1.2.2" + resolved "https://registry.npmmirror.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" + integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== + +"@types/mustache@^4.1.3": + version "4.2.2" + resolved "https://registry.npmmirror.com/@types/mustache/-/mustache-4.2.2.tgz#825bf5c214c3ab84d0b23fef2c8eb898f3ff8717" + integrity sha512-MUSpfpW0yZbTgjekDbH0shMYBUD+X/uJJJMm9LXN1d5yjl5lCY1vN/eWKD6D1tOtjA6206K0zcIPnUaFMurdNA== + +"@types/node@>=12": + version "18.16.1" + resolved "https://registry.npmmirror.com/@types/node/-/node-18.16.1.tgz#5db121e9c5352925bb1f1b892c4ae620e3526799" + integrity sha512-DZxSZWXxFfOlx7k7Rv4LAyiMroaxa3Ly/7OOzZO8cBNho0YzAi4qlbrx8W27JGqG57IgR/6J7r+nOJWw6kcvZA== + +"@types/normalize-package-data@^2.4.0": + version "2.4.1" + resolved "https://registry.npmmirror.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" + integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== + +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + +"@types/qs@^6.9.7": + version "6.9.7" + resolved "https://registry.npmmirror.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/semver@^7.3.12": + version "7.3.13" + resolved "https://registry.npmmirror.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" + integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== + +"@types/three@^0.136.1": + version "0.136.1" + resolved "https://registry.npmmirror.com/@types/three/-/three-0.136.1.tgz#030fc01cdc5ce82cad93db17b94a24515cb4b50e" + integrity sha512-gzTw6RR4dU8sGf+RpLBWWKHRVIJ4gwKVQPk+IFCgha04Efg/itXYUalX6iBcYeSmaDu0qE5M7uTq0XLQpU4FAg== + +"@types/ua-parser-js@^0.7.36": + version "0.7.36" + resolved "https://registry.npmmirror.com/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz#9bd0b47f26b5a3151be21ba4ce9f5fa457c5f190" + integrity sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ== + +"@types/web-bluetooth@^0.0.14": + version "0.0.14" + resolved "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.14.tgz#94e175b53623384bff1f354cdb3197a8d63cdbe5" + integrity sha512-5d2RhCard1nQUC3aHcq/gHzWYO6K0WJmAbjO7mQJgCQKtZpgXxv1rOM6O/dBDhDYYVutk1sciOgNSe+5YyfM8A== + +"@typescript-eslint/eslint-plugin@^5.16.0": + version "5.59.1" + resolved "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.1.tgz#9b09ee1541bff1d2cebdcb87e7ce4a4003acde08" + integrity sha512-AVi0uazY5quFB9hlp2Xv+ogpfpk77xzsgsIEWyVS7uK/c7MZ5tw7ZPbapa0SbfkqE0fsAMkz5UwtgMLVk2BQAg== + dependencies: + "@eslint-community/regexpp" "^4.4.0" + "@typescript-eslint/scope-manager" "5.59.1" + "@typescript-eslint/type-utils" "5.59.1" + "@typescript-eslint/utils" "5.59.1" + debug "^4.3.4" + grapheme-splitter "^1.0.4" + ignore "^5.2.0" + natural-compare-lite "^1.4.0" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/parser@^5.17.0": + version "5.59.1" + resolved "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-5.59.1.tgz#73c2c12127c5c1182d2e5b71a8fa2a85d215cbb4" + integrity sha512-nzjFAN8WEu6yPRDizIFyzAfgK7nybPodMNFGNH0M9tei2gYnYszRDqVA0xlnRjkl7Hkx2vYrEdb6fP2a21cG1g== + dependencies: + "@typescript-eslint/scope-manager" "5.59.1" + "@typescript-eslint/types" "5.59.1" + "@typescript-eslint/typescript-estree" "5.59.1" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@5.59.1": + version "5.59.1" + resolved "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.1.tgz#8a20222719cebc5198618a5d44113705b51fd7fe" + integrity sha512-mau0waO5frJctPuAzcxiNWqJR5Z8V0190FTSqRw1Q4Euop6+zTwHAf8YIXNwDOT29tyUDrQ65jSg9aTU/H0omA== + dependencies: + "@typescript-eslint/types" "5.59.1" + "@typescript-eslint/visitor-keys" "5.59.1" + +"@typescript-eslint/type-utils@5.59.1": + version "5.59.1" + resolved "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-5.59.1.tgz#63981d61684fd24eda2f9f08c0a47ecb000a2111" + integrity sha512-ZMWQ+Oh82jWqWzvM3xU+9y5U7MEMVv6GLioM3R5NJk6uvP47kZ7YvlgSHJ7ERD6bOY7Q4uxWm25c76HKEwIjZw== + dependencies: + "@typescript-eslint/typescript-estree" "5.59.1" + "@typescript-eslint/utils" "5.59.1" + debug "^4.3.4" + tsutils "^3.21.0" + +"@typescript-eslint/types@5.59.1": + version "5.59.1" + resolved "https://registry.npmmirror.com/@typescript-eslint/types/-/types-5.59.1.tgz#03f3fedd1c044cb336ebc34cc7855f121991f41d" + integrity sha512-dg0ICB+RZwHlysIy/Dh1SP+gnXNzwd/KS0JprD3Lmgmdq+dJAJnUPe1gNG34p0U19HvRlGX733d/KqscrGC1Pg== + +"@typescript-eslint/typescript-estree@5.59.1": + version "5.59.1" + resolved "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.1.tgz#4aa546d27fd0d477c618f0ca00b483f0ec84c43c" + integrity sha512-lYLBBOCsFltFy7XVqzX0Ju+Lh3WPIAWxYpmH/Q7ZoqzbscLiCW00LeYCdsUnnfnj29/s1WovXKh2gwCoinHNGA== + dependencies: + "@typescript-eslint/types" "5.59.1" + "@typescript-eslint/visitor-keys" "5.59.1" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.59.1": + version "5.59.1" + resolved "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-5.59.1.tgz#d89fc758ad23d2157cfae53f0b429bdf15db9473" + integrity sha512-MkTe7FE+K1/GxZkP5gRj3rCztg45bEhsd8HYjczBuYm+qFHP5vtZmjx3B0yUCDotceQ4sHgTyz60Ycl225njmA== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@types/json-schema" "^7.0.9" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.59.1" + "@typescript-eslint/types" "5.59.1" + "@typescript-eslint/typescript-estree" "5.59.1" + eslint-scope "^5.1.1" + semver "^7.3.7" + +"@typescript-eslint/visitor-keys@5.59.1": + version "5.59.1" + resolved "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.1.tgz#0d96c36efb6560d7fb8eb85de10442c10d8f6058" + integrity sha512-6waEYwBTCWryx0VJmP7JaM4FpipLsFl9CvYf2foAE8Qh/Y0s+bxWysciwOs0LTBED4JCaNxTZ5rGadB14M6dwA== + dependencies: + "@typescript-eslint/types" "5.59.1" + eslint-visitor-keys "^3.3.0" + +"@vitejs/plugin-vue@^2.2.0": + version "2.3.4" + resolved "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-2.3.4.tgz#966a6279060eb2d9d1a02ea1a331af071afdcf9e" + integrity sha512-IfFNbtkbIm36O9KB8QodlwwYvTEsJb4Lll4c2IwB3VHc2gie2mSPtSzL0eYay7X2jd/2WX02FjSGTWR6OPr/zg== + +"@volar/code-gen@0.29.8": + version "0.29.8" + resolved "https://registry.npmmirror.com/@volar/code-gen/-/code-gen-0.29.8.tgz#db1a4bf29caeba131265bed9dbe96a1a0b66ea35" + integrity sha512-eohLLUqPChHRPDFT5gXn4V6pr/CeTri7Ou5GI26lUvBRRAbP8p+oYfQRcbMPGeKmVkYjfVj0chsxQGx6T8PQ4Q== + dependencies: + "@volar/shared" "0.29.8" + "@volar/source-map" "0.29.8" + +"@volar/html2pug@0.29.8": + version "0.29.8" + resolved "https://registry.npmmirror.com/@volar/html2pug/-/html2pug-0.29.8.tgz#2e97fa2968dcdfe0dbbc67b0cd2ab4c440018738" + integrity sha512-bhSNXg8A2aD3w0B+CwmHjqCAaKtj5rORbE5C/q/UdGqptJbC6STCmi30KuRTdfPhR++Xb18Hauf3s/WCmtNAPA== + dependencies: + domelementtype "^2.2.0" + domhandler "^4.2.2" + htmlparser2 "^7.1.2" + pug "^3.0.2" + +"@volar/shared@0.29.8": + version "0.29.8" + resolved "https://registry.npmmirror.com/@volar/shared/-/shared-0.29.8.tgz#e635ddf2cbcf307da932eb4b98e33c320d3d2991" + integrity sha512-Y1NN6irkIukD+T0wf4p/dHWYL90sacN2e2lYoDXxRlvoYxwANnHgw0J0Rcp+yw58ElWRScdG7/YntEIuZWeJsw== + dependencies: + upath "^2.0.1" + vscode-jsonrpc "^8.0.0-next.2" + vscode-uri "^3.0.2" + +"@volar/source-map@0.29.8": + version "0.29.8" + resolved "https://registry.npmmirror.com/@volar/source-map/-/source-map-0.29.8.tgz#3299a0ae86ae0b72b4db3e50499d8bb285d8e9b2" + integrity sha512-7w+UoYtnc6UQu30CgMVvx0YN4dzDgP4TIsSmUaW62AGmxU9Lxwp3Kkn/4N8efi91z8ma5Z78v/HddyJPwAC3LA== + dependencies: + "@volar/shared" "0.29.8" + +"@volar/transforms@0.29.8": + version "0.29.8" + resolved "https://registry.npmmirror.com/@volar/transforms/-/transforms-0.29.8.tgz#ef807010ac90772a065e7cf50509b6433b53e355" + integrity sha512-o2hRa8CoDwYTO1Mu5KA47+1elUnYUjDaVhCvbyKlRfd8qpHea2llotArq7B6OORSL2M9DVs1IRJ5NGURBFeZ3Q== + dependencies: + "@volar/shared" "0.29.8" + vscode-languageserver "^8.0.0-next.2" + +"@volar/vue-code-gen@0.29.8": + version "0.29.8" + resolved "https://registry.npmmirror.com/@volar/vue-code-gen/-/vue-code-gen-0.29.8.tgz#32401d52e2570d775fcc6cbc83abefeef65c48cd" + integrity sha512-E1e7P2oktNC/DzgDBditfla4s8+HlUlluZ+BtcLvEdbkl3QEjujkB0x1wxguWzXmpWgLIDPtrS3Jzll5cCOkTg== + dependencies: + "@volar/code-gen" "0.29.8" + "@volar/shared" "0.29.8" + "@volar/source-map" "0.29.8" + "@vue/compiler-core" "^3.2.21" + "@vue/compiler-dom" "^3.2.21" + "@vue/shared" "^3.2.21" + upath "^2.0.1" + +"@vscode/emmet-helper@^2.8.0": + version "2.8.7" + resolved "https://registry.npmmirror.com/@vscode/emmet-helper/-/emmet-helper-2.8.7.tgz#48d95466ea12a2c0b272f08f586a648498e5d94f" + integrity sha512-y67wWaWBhlWKMX3FNOXcMPh83+xS31IqobBP6vi3HkMRxv14Bti3xgu+ya/c3oKZ0OM/QMvO+oBCwGWqbPv7Rw== + dependencies: + emmet "^2.4.2" + jsonc-parser "^2.3.0" + vscode-languageserver-textdocument "^1.0.1" + vscode-languageserver-types "^3.15.1" + vscode-uri "^2.1.2" + +"@vue/compiler-core@3.2.47", "@vue/compiler-core@^3.2.21": + version "3.2.47" + resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.2.47.tgz#3e07c684d74897ac9aa5922c520741f3029267f8" + integrity sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig== + dependencies: + "@babel/parser" "^7.16.4" + "@vue/shared" "3.2.47" + estree-walker "^2.0.2" + source-map "^0.6.1" + +"@vue/compiler-dom@3.2.47", "@vue/compiler-dom@^3.2.21": + version "3.2.47" + resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz#a0b06caf7ef7056939e563dcaa9cbde30794f305" + integrity sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ== + dependencies: + "@vue/compiler-core" "3.2.47" + "@vue/shared" "3.2.47" + +"@vue/compiler-sfc@3.2.47": + version "3.2.47" + resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.2.47.tgz#1bdc36f6cdc1643f72e2c397eb1a398f5004ad3d" + integrity sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ== + dependencies: + "@babel/parser" "^7.16.4" + "@vue/compiler-core" "3.2.47" + "@vue/compiler-dom" "3.2.47" + "@vue/compiler-ssr" "3.2.47" + "@vue/reactivity-transform" "3.2.47" + "@vue/shared" "3.2.47" + estree-walker "^2.0.2" + magic-string "^0.25.7" + postcss "^8.1.10" + source-map "^0.6.1" + +"@vue/compiler-ssr@3.2.47": + version "3.2.47" + resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.2.47.tgz#35872c01a273aac4d6070ab9d8da918ab13057ee" + integrity sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw== + dependencies: + "@vue/compiler-dom" "3.2.47" + "@vue/shared" "3.2.47" + +"@vue/devtools-api@^6.2.1", "@vue/devtools-api@^6.4.5": + version "6.5.0" + resolved "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz#98b99425edee70b4c992692628fa1ea2c1e57d07" + integrity sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q== + +"@vue/reactivity-transform@3.2.47": + version "3.2.47" + resolved "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.2.47.tgz#e45df4d06370f8abf29081a16afd25cffba6d84e" + integrity sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA== + dependencies: + "@babel/parser" "^7.16.4" + "@vue/compiler-core" "3.2.47" + "@vue/shared" "3.2.47" + estree-walker "^2.0.2" + magic-string "^0.25.7" + +"@vue/reactivity@3.2.47", "@vue/reactivity@^3.2.21": + version "3.2.47" + resolved "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.2.47.tgz#1d6399074eadfc3ed35c727e2fd707d6881140b6" + integrity sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ== + dependencies: + "@vue/shared" "3.2.47" + +"@vue/runtime-core@3.2.47": + version "3.2.47" + resolved "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.2.47.tgz#406ebade3d5551c00fc6409bbc1eeb10f32e121d" + integrity sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA== + dependencies: + "@vue/reactivity" "3.2.47" + "@vue/shared" "3.2.47" + +"@vue/runtime-dom@3.2.47": + version "3.2.47" + resolved "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.2.47.tgz#93e760eeaeab84dedfb7c3eaf3ed58d776299382" + integrity sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA== + dependencies: + "@vue/runtime-core" "3.2.47" + "@vue/shared" "3.2.47" + csstype "^2.6.8" + +"@vue/server-renderer@3.2.47": + version "3.2.47" + resolved "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.2.47.tgz#8aa1d1871fc4eb5a7851aa7f741f8f700e6de3c0" + integrity sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA== + dependencies: + "@vue/compiler-ssr" "3.2.47" + "@vue/shared" "3.2.47" + +"@vue/shared@3.2.47", "@vue/shared@^3.2.21": + version "3.2.47" + resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.2.47.tgz#e597ef75086c6e896ff5478a6bfc0a7aa4bbd14c" + integrity sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ== + +"@vueuse/core@^6.5.3": + version "6.9.2" + resolved "https://registry.npmmirror.com/@vueuse/core/-/core-6.9.2.tgz#76b16d01f33cf367dd1a2d7f2e31d106443ceb8a" + integrity sha512-FRwl4ccSFuHZBHLGgS9TMv/+Dd6XFaL4o9nph2qtgQIV+z29RBFokw08XjHfykiENRzB01MjYHJ7iRUnsIFQXg== + dependencies: + "@vueuse/shared" "6.9.2" + vue-demi "*" + +"@vueuse/core@^8.7.5": + version "8.9.4" + resolved "https://registry.npmmirror.com/@vueuse/core/-/core-8.9.4.tgz#c7db40f19390b3c9f4ff9294a30461497f62ec19" + integrity sha512-B/Mdj9TK1peFyWaPof+Zf/mP9XuGAngaJZBwPaXBvU3aCTZlx3ltlrFFFyMV4iGBwsjSCeUCgZrtkEj9dS2Y3Q== + dependencies: + "@types/web-bluetooth" "^0.0.14" + "@vueuse/metadata" "8.9.4" + "@vueuse/shared" "8.9.4" + vue-demi "*" + +"@vueuse/metadata@8.9.4": + version "8.9.4" + resolved "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-8.9.4.tgz#a4132db33e4c1b1023636acfa20aa7b37ab3d978" + integrity sha512-IwSfzH80bnJMzqhaapqJl9JRIiyQU0zsRGEgnxN6jhq7992cPUJIRfV+JHRIZXjYqbwt07E1gTEp0R0zPJ1aqw== + +"@vueuse/shared@6.9.2": + version "6.9.2" + resolved "https://registry.npmmirror.com/@vueuse/shared/-/shared-6.9.2.tgz#97e4369fa7262ebc96fe1d6e210268f30b037005" + integrity sha512-lAiMh6XROs0kSKVd0Yb/6GKoQMxC1fYrFDi6opvQWISPtcqRNluRrQxLUZ3WTI78ovtoKRLktjhkFAtydcfFDg== + dependencies: + vue-demi "*" + +"@vueuse/shared@8.9.4": + version "8.9.4" + resolved "https://registry.npmmirror.com/@vueuse/shared/-/shared-8.9.4.tgz#c9741c30ffb666b50d62f0dd80b76119fd47573e" + integrity sha512-wt+T30c4K6dGRMVqPddexEVLa28YwxW5OFIPmzUHICjphfAuBFTTdDoyqREZNDOFJZ44ARH1WWQNCUK8koJ+Ag== + dependencies: + vue-demi "*" + +JSONStream@^1.0.4: + version "1.3.5" + resolved "https://registry.npmmirror.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" + integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" + +abbrev@^1.0.0, abbrev@~1.1.1: + version "1.1.1" + resolved "https://registry.npmmirror.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^7.1.1: + version "7.4.1" + resolved "https://registry.npmmirror.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +acorn@^8.4.1, acorn@^8.8.0: + version "8.8.2" + resolved "https://registry.npmmirror.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" + integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== + +agent-base@6, agent-base@^6.0.2: + version "6.0.2" + resolved "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +agentkeepalive@^4.2.1: + version "4.3.0" + resolved "https://registry.npmmirror.com/agentkeepalive/-/agentkeepalive-4.3.0.tgz#bb999ff07412653c1803b3ced35e50729830a255" + integrity sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg== + dependencies: + debug "^4.1.0" + depd "^2.0.0" + humanize-ms "^1.2.1" + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.npmmirror.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.6: + version "6.12.6" + resolved "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^4.3.0: + version "4.3.2" + resolved "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0, ansi-styles@^4.3.0: + version "4.3.0" + resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^6.0.0: + version "6.2.1" + resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + +ant-design-vue@2.2.8: + version "2.2.8" + resolved "https://registry.npmmirror.com/ant-design-vue/-/ant-design-vue-2.2.8.tgz#fa87cf6842d8ee9a0d8af393ff4099ecc4072f2b" + integrity sha512-3graq9/gCfJQs6hznrHV6sa9oDmk/D1H3Oo0vLdVpPS/I61fZPk8NEyNKCHpNA6fT2cx6xx9U3QS63uuyikg/Q== + dependencies: + "@ant-design/icons-vue" "^6.0.0" + "@babel/runtime" "^7.10.5" + "@simonwep/pickr" "~1.8.0" + array-tree-filter "^2.1.0" + async-validator "^3.3.0" + dom-align "^1.12.1" + dom-scroll-into-view "^2.0.0" + lodash "^4.17.21" + lodash-es "^4.17.15" + moment "^2.27.0" + omit.js "^2.0.0" + resize-observer-polyfill "^1.5.1" + scroll-into-view-if-needed "^2.2.25" + shallow-equal "^1.0.0" + vue-types "^3.0.0" + warning "^4.0.0" + +"aproba@^1.0.3 || ^2.0.0", aproba@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +archy@~1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" + integrity sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw== + +are-we-there-yet@^3.0.0: + version "3.0.1" + resolved "https://registry.npmmirror.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" + integrity sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.npmmirror.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-ify@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" + integrity sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng== + +array-tree-filter@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/array-tree-filter/-/array-tree-filter-2.1.0.tgz#873ac00fec83749f255ac8dd083814b4f6329190" + integrity sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +arrify@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== + +asap@^2.0.0, asap@~2.0.3: + version "2.0.6" + resolved "https://registry.npmmirror.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== + +assert-never@^1.2.1: + version "1.2.1" + resolved "https://registry.npmmirror.com/assert-never/-/assert-never-1.2.1.tgz#11f0e363bf146205fb08193b5c7b90f4d1cf44fe" + integrity sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw== + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +async-validator@^3.3.0: + version "3.5.2" + resolved "https://registry.npmmirror.com/async-validator/-/async-validator-3.5.2.tgz#68e866a96824e8b2694ff7a831c1a25c44d5e500" + integrity sha512-8eLCg00W9pIRZSB781UUX/H6Oskmm8xloZfr09lz5bikRpBVDlJ3hRVuxxP1SxcwsEYfJ4IU8Q19Y8/893r3rQ== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +axios@*: + version "1.3.6" + resolved "https://registry.npmmirror.com/axios/-/axios-1.3.6.tgz#1ace9a9fb994314b5f6327960918406fa92c6646" + integrity sha512-PEcdkk7JcdPiMDkvM4K6ZBRYq9keuVJsToxm2zQIM70Qqo2WHTdJZMXcG9X+RmRp2VPNUQC8W1RAGbgt6b1yMg== + dependencies: + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +axios@^0.26.1: + version "0.26.1" + resolved "https://registry.npmmirror.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9" + integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA== + dependencies: + follow-redirects "^1.14.8" + +babel-walk@3.0.0-canary-5: + version "3.0.0-canary-5" + resolved "https://registry.npmmirror.com/babel-walk/-/babel-walk-3.0.0-canary-5.tgz#f66ecd7298357aee44955f235a6ef54219104b11" + integrity sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw== + dependencies: + "@babel/types" "^7.9.6" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +bin-links@^3.0.3: + version "3.0.3" + resolved "https://registry.npmmirror.com/bin-links/-/bin-links-3.0.3.tgz#3842711ef3db2cd9f16a5f404a996a12db355a6e" + integrity sha512-zKdnMPWEdh4F5INR07/eBrodC7QrF5JKvqskjz/ZZRXg5YSAZIbn8zGhbhUrElzHBZ2fvEQdOU59RHcTG3GiwA== + dependencies: + cmd-shim "^5.0.0" + mkdirp-infer-owner "^2.0.0" + npm-normalize-package-bin "^2.0.0" + read-cmd-shim "^3.0.0" + rimraf "^3.0.0" + write-file-atomic "^4.0.0" + +binary-extensions@^2.2.0: + version "2.2.0" + resolved "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.2: + version "3.0.2" + resolved "https://registry.npmmirror.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +builtins@^5.0.0: + version "5.0.1" + resolved "https://registry.npmmirror.com/builtins/-/builtins-5.0.1.tgz#87f6db9ab0458be728564fa81d876d8d74552fa9" + integrity sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ== + dependencies: + semver "^7.0.0" + +cacache@^16.0.0, cacache@^16.1.0, cacache@^16.1.3: + version "16.1.3" + resolved "https://registry.npmmirror.com/cacache/-/cacache-16.1.3.tgz#a02b9f34ecfaf9a78c9f4bc16fceb94d5d67a38e" + integrity sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ== + dependencies: + "@npmcli/fs" "^2.1.0" + "@npmcli/move-file" "^2.0.0" + chownr "^2.0.0" + fs-minipass "^2.1.0" + glob "^8.0.1" + infer-owner "^1.0.4" + lru-cache "^7.7.1" + minipass "^3.1.6" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + mkdirp "^1.0.4" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^9.0.0" + tar "^6.1.11" + unique-filename "^2.0.0" + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase-keys@^6.2.2: + version "6.2.2" + resolved "https://registry.npmmirror.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" + integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== + dependencies: + camelcase "^5.3.1" + map-obj "^4.0.0" + quick-lru "^4.0.1" + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +character-parser@^2.2.0: + version "2.2.0" + resolved "https://registry.npmmirror.com/character-parser/-/character-parser-2.2.0.tgz#c7ce28f36d4bcd9744e5ffc2c5fcde1c73261fc0" + integrity sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw== + dependencies: + is-regex "^1.0.3" + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +cidr-regex@^3.1.1: + version "3.1.1" + resolved "https://registry.npmmirror.com/cidr-regex/-/cidr-regex-3.1.1.tgz#ba1972c57c66f61875f18fd7dd487469770b571d" + integrity sha512-RBqYd32aDwbCMFJRL6wHOlDNYJsPNTt8vC82ErHF5vKt8QQzxm1FrkW8s/R5pVrXMf17sba09Uoy91PKiddAsw== + dependencies: + ip-regex "^4.1.0" + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.npmmirror.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-columns@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/cli-columns/-/cli-columns-4.0.0.tgz#9fe4d65975238d55218c41bd2ed296a7fa555646" + integrity sha512-XW2Vg+w+L9on9wtwKpyzluIPCWXjaBahI7mTcYjx+BVIYD9c3yqcv/yKC7CmdCZat4rq2yiE1UMSJC5ivKfMtQ== + dependencies: + string-width "^4.2.3" + strip-ansi "^6.0.1" + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-table3@^0.6.2: + version "0.6.3" + resolved "https://registry.npmmirror.com/cli-table3/-/cli-table3-0.6.3.tgz#61ab765aac156b52f222954ffc607a6f01dbeeb2" + integrity sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg== + dependencies: + string-width "^4.2.0" + optionalDependencies: + "@colors/colors" "1.5.0" + +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + +cli-truncate@^3.1.0: + version "3.1.0" + resolved "https://registry.npmmirror.com/cli-truncate/-/cli-truncate-3.1.0.tgz#3f23ab12535e3d73e839bb43e73c9de487db1389" + integrity sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA== + dependencies: + slice-ansi "^5.0.0" + string-width "^5.0.0" + +clipboard@^2.0.0: + version "2.0.11" + resolved "https://registry.npmmirror.com/clipboard/-/clipboard-2.0.11.tgz#62180360b97dd668b6b3a84ec226975762a70be5" + integrity sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw== + dependencies: + good-listener "^1.2.2" + select "^1.1.2" + tiny-emitter "^2.0.0" + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.npmmirror.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== + +cmd-shim@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/cmd-shim/-/cmd-shim-5.0.0.tgz#8d0aaa1a6b0708630694c4dbde070ed94c707724" + integrity sha512-qkCtZ59BidfEwHltnJwkyVZn+XQojdAySM1D1gSeh11Z4pW1Kpolkyo53L5noc0nrxmIvyFwTmJRo4xs7FFLPw== + dependencies: + mkdirp-infer-owner "^2.0.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-support@^1.1.3: + version "1.1.3" + resolved "https://registry.npmmirror.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + +colord@^2.9.2: + version "2.9.3" + resolved "https://registry.npmmirror.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" + integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== + +colorette@^2.0.16: + version "2.0.20" + resolved "https://registry.npmmirror.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +columnify@^1.6.0: + version "1.6.0" + resolved "https://registry.npmmirror.com/columnify/-/columnify-1.6.0.tgz#6989531713c9008bb29735e61e37acf5bd553cf3" + integrity sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q== + dependencies: + strip-ansi "^6.0.1" + wcwidth "^1.0.0" + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@^9.3.0: + version "9.5.0" + resolved "https://registry.npmmirror.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" + integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== + +common-ancestor-path@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7" + integrity sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w== + +compare-func@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/compare-func/-/compare-func-2.0.0.tgz#fb65e75edbddfd2e568554e8b5b05fff7a51fcb3" + integrity sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA== + dependencies: + array-ify "^1.0.0" + dot-prop "^5.1.0" + +compute-scroll-into-view@^1.0.20: + version "1.0.20" + resolved "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz#1768b5522d1172754f5d0c9b02de3af6be506a43" + integrity sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + +constantinople@^4.0.1: + version "4.0.1" + resolved "https://registry.npmmirror.com/constantinople/-/constantinople-4.0.1.tgz#0def113fa0e4dc8de83331a5cf79c8b325213151" + integrity sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw== + dependencies: + "@babel/parser" "^7.6.0" + "@babel/types" "^7.6.1" + +conventional-changelog-angular@^5.0.11: + version "5.0.13" + resolved "https://registry.npmmirror.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz#896885d63b914a70d4934b59d2fe7bde1832b28c" + integrity sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA== + dependencies: + compare-func "^2.0.0" + q "^1.5.1" + +conventional-changelog-conventionalcommits@^4.3.1: + version "4.6.3" + resolved "https://registry.npmmirror.com/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.6.3.tgz#0765490f56424b46f6cb4db9135902d6e5a36dc2" + integrity sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g== + dependencies: + compare-func "^2.0.0" + lodash "^4.17.15" + q "^1.5.1" + +conventional-commits-parser@^3.2.2: + version "3.2.4" + resolved "https://registry.npmmirror.com/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz#a7d3b77758a202a9b2293d2112a8d8052c740972" + integrity sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q== + dependencies: + JSONStream "^1.0.4" + is-text-path "^1.0.1" + lodash "^4.17.15" + meow "^8.0.0" + split2 "^3.0.0" + through2 "^4.0.0" + +copy-anything@^2.0.1: + version "2.0.6" + resolved "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz#092454ea9584a7b7ad5573062b2a87f5900fc480" + integrity sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw== + dependencies: + is-what "^3.14.1" + +core-js@^3.15.1: + version "3.30.1" + resolved "https://registry.npmmirror.com/core-js/-/core-js-3.30.1.tgz#fc9c5adcc541d8e9fa3e381179433cbf795628ba" + integrity sha512-ZNS5nbiSwDTq4hFosEDqm65izl2CWmLz0hARJMyNQBgkUZMIF51cQiMvIQKA6hvuaeWxQDP3hEedM1JZIgTldQ== + +cosmiconfig-typescript-loader@^2.0.0: + version "2.0.2" + resolved "https://registry.npmmirror.com/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-2.0.2.tgz#7e7ce6064af041c910e1e43fb0fd9625cee56e93" + integrity sha512-KmE+bMjWMXJbkWCeY4FJX/npHuZPNr9XF9q9CIQ/bpFwi1qHfCmSiKarrCcRa0LO4fWjk93pVoeRtJAkTGcYNw== + dependencies: + cosmiconfig "^7" + ts-node "^10.8.1" + +cosmiconfig@^7, cosmiconfig@^7.0.0: + version "7.1.0" + resolved "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" + integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.npmmirror.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-env@^7.0.3: + version "7.0.3" + resolved "https://registry.npmmirror.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== + dependencies: + cross-spawn "^7.0.1" + +cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +csstype@^2.6.8: + version "2.6.21" + resolved "https://registry.npmmirror.com/csstype/-/csstype-2.6.21.tgz#2efb85b7cc55c80017c66a5ad7cbd931fda3a90e" + integrity sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w== + +dargs@^7.0.0: + version "7.0.0" + resolved "https://registry.npmmirror.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" + integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== + +dayjs@^1.11.1: + version "1.11.7" + resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2" + integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ== + +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +debug@^3.2.6: + version "3.2.7" + resolved "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debuglog@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" + integrity sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw== + +decamelize-keys@^1.1.0: + version "1.1.1" + resolved "https://registry.npmmirror.com/decamelize-keys/-/decamelize-keys-1.1.1.tgz#04a2d523b2f18d80d0158a43b895d56dff8d19d8" + integrity sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg== + dependencies: + decamelize "^1.1.0" + map-obj "^1.0.0" + +decamelize@^1.1.0: + version "1.2.0" + resolved "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== + +decode-uri-component@^0.2.2: + version "0.2.2" + resolved "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" + integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +defaults@^1.0.3: + version "1.0.4" + resolved "https://registry.npmmirror.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" + integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== + dependencies: + clone "^1.0.2" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +delegate@^3.1.2: + version "3.2.0" + resolved "https://registry.npmmirror.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166" + integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw== + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + +depd@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +dezalgo@^1.0.0: + version "1.0.4" + resolved "https://registry.npmmirror.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81" + integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== + dependencies: + asap "^2.0.0" + wrappy "1" + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.npmmirror.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +diff@^5.1.0: + version "5.1.0" + resolved "https://registry.npmmirror.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" + integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +doctypes@^1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9" + integrity sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ== + +dom-align@^1.12.1: + version "1.12.4" + resolved "https://registry.npmmirror.com/dom-align/-/dom-align-1.12.4.tgz#3503992eb2a7cfcb2ed3b2a6d21e0b9c00d54511" + integrity sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw== + +dom-scroll-into-view@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/dom-scroll-into-view/-/dom-scroll-into-view-2.0.1.tgz#0decc8522801fd8d3f1c6ba355a74d382c5f989b" + integrity sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w== + +dom-serializer@^1.0.1: + version "1.4.1" + resolved "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" + integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + +domelementtype@^2.0.1, domelementtype@^2.2.0: + version "2.3.0" + resolved "https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^4.2.0, domhandler@^4.2.2: + version "4.3.1" + resolved "https://registry.npmmirror.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" + integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== + dependencies: + domelementtype "^2.2.0" + +domutils@^2.8.0: + version "2.8.0" + resolved "https://registry.npmmirror.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + +dot-prop@^5.1.0: + version "5.3.0" + resolved "https://registry.npmmirror.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" + integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== + dependencies: + is-obj "^2.0.0" + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +emmet@^2.4.2: + version "2.4.2" + resolved "https://registry.npmmirror.com/emmet/-/emmet-2.4.2.tgz#686983e7b8623ad582fc9fbb6515b17d195c0394" + integrity sha512-YgmsMkhUgzhJMgH5noGudfxqrQn1bapvF0y7C1e7A0jWFImsRrrvVslzyZz0919NED/cjFOpVWx7c973V+2S/w== + dependencies: + "@emmetio/abbreviation" "^2.3.1" + "@emmetio/css-abbreviation" "^2.1.6" + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +encoding@^0.1.13: + version "0.1.13" + resolved "https://registry.npmmirror.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" + +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.npmmirror.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + +entities@^3.0.1: + version "3.0.1" + resolved "https://registry.npmmirror.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4" + integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q== + +env-paths@^2.2.0: + version "2.2.1" + resolved "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + +err-code@^2.0.2: + version "2.0.3" + resolved "https://registry.npmmirror.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" + integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + +errno@^0.1.1: + version "0.1.8" + resolved "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" + integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== + dependencies: + prr "~1.0.1" + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +esbuild-android-64@0.14.54: + version "0.14.54" + resolved "https://registry.npmmirror.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz#505f41832884313bbaffb27704b8bcaa2d8616be" + integrity sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ== + +esbuild-android-arm64@0.14.54: + version "0.14.54" + resolved "https://registry.npmmirror.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz#8ce69d7caba49646e009968fe5754a21a9871771" + integrity sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg== + +esbuild-darwin-64@0.14.54: + version "0.14.54" + resolved "https://registry.npmmirror.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz#24ba67b9a8cb890a3c08d9018f887cc221cdda25" + integrity sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug== + +esbuild-darwin-arm64@0.14.54: + version "0.14.54" + resolved "https://registry.npmmirror.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz#3f7cdb78888ee05e488d250a2bdaab1fa671bf73" + integrity sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw== + +esbuild-freebsd-64@0.14.54: + version "0.14.54" + resolved "https://registry.npmmirror.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz#09250f997a56ed4650f3e1979c905ffc40bbe94d" + integrity sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg== + +esbuild-freebsd-arm64@0.14.54: + version "0.14.54" + resolved "https://registry.npmmirror.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz#bafb46ed04fc5f97cbdb016d86947a79579f8e48" + integrity sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q== + +esbuild-linux-32@0.14.54: + version "0.14.54" + resolved "https://registry.npmmirror.com/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz#e2a8c4a8efdc355405325033fcebeb941f781fe5" + integrity sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw== + +esbuild-linux-64@0.14.54: + version "0.14.54" + resolved "https://registry.npmmirror.com/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz#de5fdba1c95666cf72369f52b40b03be71226652" + integrity sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg== + +esbuild-linux-arm64@0.14.54: + version "0.14.54" + resolved "https://registry.npmmirror.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz#dae4cd42ae9787468b6a5c158da4c84e83b0ce8b" + integrity sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig== + +esbuild-linux-arm@0.14.54: + version "0.14.54" + resolved "https://registry.npmmirror.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz#a2c1dff6d0f21dbe8fc6998a122675533ddfcd59" + integrity sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw== + +esbuild-linux-mips64le@0.14.54: + version "0.14.54" + resolved "https://registry.npmmirror.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz#d9918e9e4cb972f8d6dae8e8655bf9ee131eda34" + integrity sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw== + +esbuild-linux-ppc64le@0.14.54: + version "0.14.54" + resolved "https://registry.npmmirror.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz#3f9a0f6d41073fb1a640680845c7de52995f137e" + integrity sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ== + +esbuild-linux-riscv64@0.14.54: + version "0.14.54" + resolved "https://registry.npmmirror.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz#618853c028178a61837bc799d2013d4695e451c8" + integrity sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg== + +esbuild-linux-s390x@0.14.54: + version "0.14.54" + resolved "https://registry.npmmirror.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz#d1885c4c5a76bbb5a0fe182e2c8c60eb9e29f2a6" + integrity sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA== + +esbuild-netbsd-64@0.14.54: + version "0.14.54" + resolved "https://registry.npmmirror.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz#69ae917a2ff241b7df1dbf22baf04bd330349e81" + integrity sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w== + +esbuild-openbsd-64@0.14.54: + version "0.14.54" + resolved "https://registry.npmmirror.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz#db4c8495287a350a6790de22edea247a57c5d47b" + integrity sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw== + +esbuild-sunos-64@0.14.54: + version "0.14.54" + resolved "https://registry.npmmirror.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz#54287ee3da73d3844b721c21bc80c1dc7e1bf7da" + integrity sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw== + +esbuild-windows-32@0.14.54: + version "0.14.54" + resolved "https://registry.npmmirror.com/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz#f8aaf9a5667630b40f0fb3aa37bf01bbd340ce31" + integrity sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w== + +esbuild-windows-64@0.14.54: + version "0.14.54" + resolved "https://registry.npmmirror.com/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz#bf54b51bd3e9b0f1886ffdb224a4176031ea0af4" + integrity sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ== + +esbuild-windows-arm64@0.14.54: + version "0.14.54" + resolved "https://registry.npmmirror.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz#937d15675a15e4b0e4fafdbaa3a01a776a2be982" + integrity sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg== + +esbuild@^0.14.27: + version "0.14.54" + resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.14.54.tgz#8b44dcf2b0f1a66fc22459943dccf477535e9aa2" + integrity sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA== + optionalDependencies: + "@esbuild/linux-loong64" "0.14.54" + esbuild-android-64 "0.14.54" + esbuild-android-arm64 "0.14.54" + esbuild-darwin-64 "0.14.54" + esbuild-darwin-arm64 "0.14.54" + esbuild-freebsd-64 "0.14.54" + esbuild-freebsd-arm64 "0.14.54" + esbuild-linux-32 "0.14.54" + esbuild-linux-64 "0.14.54" + esbuild-linux-arm "0.14.54" + esbuild-linux-arm64 "0.14.54" + esbuild-linux-mips64le "0.14.54" + esbuild-linux-ppc64le "0.14.54" + esbuild-linux-riscv64 "0.14.54" + esbuild-linux-s390x "0.14.54" + esbuild-netbsd-64 "0.14.54" + esbuild-openbsd-64 "0.14.54" + esbuild-sunos-64 "0.14.54" + esbuild-windows-32 "0.14.54" + esbuild-windows-64 "0.14.54" + esbuild-windows-arm64 "0.14.54" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-prettier@^8.5.0: + version "8.8.0" + resolved "https://registry.npmmirror.com/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz#bfda738d412adc917fd7b038857110efe98c9348" + integrity sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA== + +eslint-define-config@^1.3.0: + version "1.20.0" + resolved "https://registry.npmmirror.com/eslint-define-config/-/eslint-define-config-1.20.0.tgz#03881929aea6148660f843cc05c5b9b08c08b2d1" + integrity sha512-JLhyTcY5eOUGXoPP4Ldnf1TMOllRt4vbvuAtkwq+ugU0JGDqosdRLYWMtn9NjYAd9rApR5EcItdSmpY6FsILew== + +eslint-plugin-prettier@^4.0.0: + version "4.2.1" + resolved "https://registry.npmmirror.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" + integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== + dependencies: + prettier-linter-helpers "^1.0.0" + +eslint-plugin-vue@^8.5.0: + version "8.7.1" + resolved "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-8.7.1.tgz#f13c53547a0c9d64588a675cc5ecc6ccaf63703f" + integrity sha512-28sbtm4l4cOzoO1LtzQPxfxhQABararUb1JtqusQqObJpWX2e/gmVyeYVfepizPFne0Q5cILkYGiBoV36L12Wg== + dependencies: + eslint-utils "^3.0.0" + natural-compare "^1.4.0" + nth-check "^2.0.1" + postcss-selector-parser "^6.0.9" + semver "^7.3.5" + vue-eslint-parser "^8.0.1" + +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-scope@^7.0.0, eslint-scope@^7.2.0: + version "7.2.0" + resolved "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-7.2.0.tgz#f21ebdafda02352f103634b96dd47d9f81ca117b" + integrity sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + +eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint-visitor-keys@^3.1.0, eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.0: + version "3.4.0" + resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc" + integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ== + +eslint@^8.11.0: + version "8.39.0" + resolved "https://registry.npmmirror.com/eslint/-/eslint-8.39.0.tgz#7fd20a295ef92d43809e914b70c39fd5a23cf3f1" + integrity sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.4.0" + "@eslint/eslintrc" "^2.0.2" + "@eslint/js" "8.39.0" + "@humanwhocodes/config-array" "^0.11.8" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.0" + eslint-visitor-keys "^3.4.0" + espree "^9.5.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + grapheme-splitter "^1.0.4" + ignore "^5.2.0" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-sdsl "^4.1.4" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.1" + strip-ansi "^6.0.1" + strip-json-comments "^3.1.0" + text-table "^0.2.0" + +espree@^9.0.0, espree@^9.5.1: + version "9.5.1" + resolved "https://registry.npmmirror.com/espree/-/espree-9.5.1.tgz#4f26a4d5f18905bf4f2e0bd99002aab807e96dd4" + integrity sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg== + dependencies: + acorn "^8.8.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.0" + +esquery@^1.4.0, esquery@^1.4.2: + version "1.5.0" + resolved "https://registry.npmmirror.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.npmmirror.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +estree-walker@^2.0.2: + version "2.0.2" + resolved "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +execa@^5.0.0, execa@^5.1.1: + version "5.1.1" + resolved "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.2.0" + resolved "https://registry.npmmirror.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + +fast-glob@^3.2.9: + version "3.2.12" + resolved "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" + integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastest-levenshtein@^1.0.12: + version "1.0.16" + resolved "https://registry.npmmirror.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" + integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== + +fastq@^1.6.0: + version "1.15.0" + resolved "https://registry.npmmirror.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" + integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== + dependencies: + reusify "^1.0.4" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +filter-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" + integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== + +find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.npmmirror.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flatted@^3.1.0: + version "3.2.7" + resolved "https://registry.npmmirror.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" + integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== + +follow-redirects@^1.14.8, follow-redirects@^1.15.0: + version "1.15.2" + resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" + integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +fs-extra@^10.0.0: + version "10.1.0" + resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-minipass@^2.0.0, fs-minipass@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +gauge@^4.0.3: + version "4.0.4" + resolved "https://registry.npmmirror.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" + integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.3" + console-control-strings "^1.1.0" + has-unicode "^2.0.1" + signal-exit "^3.0.7" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.5" + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.0.2: + version "1.2.0" + resolved "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f" + integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +git-raw-commits@^2.0.0: + version "2.0.11" + resolved "https://registry.npmmirror.com/git-raw-commits/-/git-raw-commits-2.0.11.tgz#bc3576638071d18655e1cc60d7f524920008d723" + integrity sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A== + dependencies: + dargs "^7.0.0" + lodash "^4.17.15" + meow "^8.0.0" + split2 "^3.0.0" + through2 "^4.0.0" + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^8.0.1: + version "8.1.0" + resolved "https://registry.npmmirror.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + +global-dirs@^0.1.1: + version "0.1.1" + resolved "https://registry.npmmirror.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" + integrity sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg== + dependencies: + ini "^1.3.4" + +globals@^13.19.0: + version "13.20.0" + resolved "https://registry.npmmirror.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" + integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== + dependencies: + type-fest "^0.20.2" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.npmmirror.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +good-listener@^1.2.2: + version "1.2.2" + resolved "https://registry.npmmirror.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" + integrity sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw== + dependencies: + delegate "^3.1.2" + +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.6: + version "4.2.11" + resolved "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +gradient-parser@^1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/gradient-parser/-/gradient-parser-1.0.2.tgz#d283b80390386e2613c992bb0e5abb259aedf25f" + integrity sha512-gR6nY33xC9yJoH4wGLQtZQMXDi6RI3H37ERu7kQCVUzlXjNedpZM7xcA489Opwbq0BSGohtWGsWsntupmxelMg== + +grapheme-splitter@^1.0.4: + version "1.0.4" + resolved "https://registry.npmmirror.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" + integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== + +hard-rejection@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" + integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.npmmirror.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.npmmirror.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hosted-git-info@^2.1.4: + version "2.8.9" + resolved "https://registry.npmmirror.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== + +hosted-git-info@^4.0.1: + version "4.1.0" + resolved "https://registry.npmmirror.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224" + integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA== + dependencies: + lru-cache "^6.0.0" + +hosted-git-info@^5.0.0, hosted-git-info@^5.2.1: + version "5.2.1" + resolved "https://registry.npmmirror.com/hosted-git-info/-/hosted-git-info-5.2.1.tgz#0ba1c97178ef91f3ab30842ae63d6a272341156f" + integrity sha512-xIcQYMnhcx2Nr4JTjsFmwwnr9vldugPy9uVm0o87bjqqWMv9GaqsTeT+i99wTl0mk1uLxJtHxLb8kymqTENQsw== + dependencies: + lru-cache "^7.5.1" + +hotkeys-js@^3.8.7: + version "3.10.2" + resolved "https://registry.npmmirror.com/hotkeys-js/-/hotkeys-js-3.10.2.tgz#cf52661904f5a13a973565cb97085fea2f5ae257" + integrity sha512-Z6vLmJTYzkbZZXlBkhrYB962Q/rZGc/WHQiyEGu9ZZVF7bAeFDjjDa31grWREuw9Ygb4zmlov2bTkPYqj0aFnQ== + +htmlparser2@^7.1.2: + version "7.2.0" + resolved "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-7.2.0.tgz#8817cdea38bbc324392a90b1990908e81a65f5a5" + integrity sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.2" + domutils "^2.8.0" + entities "^3.0.1" + +http-cache-semantics@^4.1.0: + version "4.1.1" + resolved "https://registry.npmmirror.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== + +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.npmmirror.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + +husky@^7.0.4: + version "7.0.4" + resolved "https://registry.npmmirror.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535" + integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ== + +i@^0.3.7: + version "0.3.7" + resolved "https://registry.npmmirror.com/i/-/i-0.3.7.tgz#2a7437a923d59c14b17243dc63a549af24d85799" + integrity sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q== + +iconv-lite@^0.6.2, iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ignore-walk@^5.0.1: + version "5.0.1" + resolved "https://registry.npmmirror.com/ignore-walk/-/ignore-walk-5.0.1.tgz#5f199e23e1288f518d90358d461387788a154776" + integrity sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw== + dependencies: + minimatch "^5.0.1" + +ignore@^5.2.0: + version "5.2.4" + resolved "https://registry.npmmirror.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== + +image-size@~0.5.0: + version "0.5.5" + resolved "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" + integrity sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ== + +import-fresh@^3.0.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +infer-owner@^1.0.4: + version "1.0.4" + resolved "https://registry.npmmirror.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3: + version "2.0.4" + resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@^1.3.4: + version "1.3.8" + resolved "https://registry.npmmirror.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +ini@^3.0.0, ini@^3.0.1: + version "3.0.1" + resolved "https://registry.npmmirror.com/ini/-/ini-3.0.1.tgz#c76ec81007875bc44d544ff7a11a55d12294102d" + integrity sha512-it4HyVAUTKBc6m8e1iXWvXSTdndF7HbdN713+kvLrymxTaU4AUBWrJ4vEooP+V7fexnVD3LKcBshjGGPefSMUQ== + +init-package-json@^3.0.2: + version "3.0.2" + resolved "https://registry.npmmirror.com/init-package-json/-/init-package-json-3.0.2.tgz#f5bc9bac93f2bdc005778bc2271be642fecfcd69" + integrity sha512-YhlQPEjNFqlGdzrBfDNRLhvoSgX7iQRgSxgsNknRQ9ITXFT7UMfVMWhBTOh2Y+25lRnGrv5Xz8yZwQ3ACR6T3A== + dependencies: + npm-package-arg "^9.0.1" + promzard "^0.3.0" + read "^1.0.7" + read-package-json "^5.0.0" + semver "^7.3.5" + validate-npm-package-license "^3.0.4" + validate-npm-package-name "^4.0.0" + +interactjs@^1.10.11: + version "1.10.17" + resolved "https://registry.npmmirror.com/interactjs/-/interactjs-1.10.17.tgz#aed66a63020cd092236133f9149e6448dc405d72" + integrity sha512-grjHJgnWkCoQLmAlk2yalNd1r0ztUhXLJNVjSOfWn1wfNNgU2tx1cDEkro9WYerDNC9UG3MZTeD4O6zOM5gbIA== + dependencies: + "@interactjs/types" "1.10.17" + +ip-regex@^4.1.0: + version "4.3.0" + resolved "https://registry.npmmirror.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5" + integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q== + +ip@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" + integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-cidr@^4.0.2: + version "4.0.2" + resolved "https://registry.npmmirror.com/is-cidr/-/is-cidr-4.0.2.tgz#94c7585e4c6c77ceabf920f8cde51b8c0fda8814" + integrity sha512-z4a1ENUajDbEl/Q6/pVBpTR1nBjjEE1X7qb7bmWYanNnPoKAvUCPFKeXV6Fe4mgTkWKBqiHIcwsI3SndiO5FeA== + dependencies: + cidr-regex "^3.1.1" + +is-core-module@^2.11.0, is-core-module@^2.5.0, is-core-module@^2.8.1: + version "2.12.0" + resolved "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.12.0.tgz#36ad62f6f73c8253fd6472517a12483cf03e7ec4" + integrity sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ== + dependencies: + has "^1.0.3" + +is-expression@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/is-expression/-/is-expression-4.0.0.tgz#c33155962abf21d0afd2552514d67d2ec16fd2ab" + integrity sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A== + dependencies: + acorn "^7.1.1" + object-assign "^4.1.1" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-fullwidth-code-point@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88" + integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-lambda@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" + integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.npmmirror.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-plain-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== + +is-plain-object@3.0.1: + version "3.0.1" + resolved "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-3.0.1.tgz#662d92d24c0aa4302407b0d45d21f2251c85f85b" + integrity sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g== + +is-plain-object@5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + +is-promise@^2.0.0: + version "2.2.2" + resolved "https://registry.npmmirror.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" + integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== + +is-regex@^1.0.3: + version "1.1.4" + resolved "https://registry.npmmirror.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-text-path@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e" + integrity sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w== + dependencies: + text-extensions "^1.0.0" + +is-what@^3.14.1: + version "3.14.1" + resolved "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz#e1222f46ddda85dead0fd1c9df131760e77755c1" + integrity sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +js-cookie@^3.0.1: + version "3.0.5" + resolved "https://registry.npmmirror.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc" + integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw== + +js-sdsl@^4.1.4: + version "4.4.0" + resolved "https://registry.npmmirror.com/js-sdsl/-/js-sdsl-4.4.0.tgz#8b437dbe642daa95760400b602378ed8ffea8430" + integrity sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg== + +js-stringify@^1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/js-stringify/-/js-stringify-1.0.2.tgz#1736fddfd9724f28a3682adc6230ae7e4e9679db" + integrity sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g== + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: + version "2.3.1" + resolved "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json-stringify-nice@^1.1.4: + version "1.1.4" + resolved "https://registry.npmmirror.com/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz#2c937962b80181d3f317dd39aa323e14f5a60a67" + integrity sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw== + +jsonc-parser@^2.3.0: + version "2.3.1" + resolved "https://registry.npmmirror.com/jsonc-parser/-/jsonc-parser-2.3.1.tgz#59549150b133f2efacca48fe9ce1ec0659af2342" + integrity sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg== + +jsonc-parser@^3.0.0: + version "3.2.0" + resolved "https://registry.npmmirror.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" + integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +jsonparse@^1.2.0, jsonparse@^1.3.1: + version "1.3.1" + resolved "https://registry.npmmirror.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" + integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== + +jstransformer@1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/jstransformer/-/jstransformer-1.0.0.tgz#ed8bf0921e2f3f1ed4d5c1a44f68709ed24722c3" + integrity sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A== + dependencies: + is-promise "^2.0.0" + promise "^7.0.1" + +just-diff-apply@^5.2.0: + version "5.5.0" + resolved "https://registry.npmmirror.com/just-diff-apply/-/just-diff-apply-5.5.0.tgz#771c2ca9fa69f3d2b54e7c3f5c1dfcbcc47f9f0f" + integrity sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw== + +just-diff@^5.0.1: + version "5.2.0" + resolved "https://registry.npmmirror.com/just-diff/-/just-diff-5.2.0.tgz#60dca55891cf24cd4a094e33504660692348a241" + integrity sha512-6ufhP9SHjb7jibNFrNxyFZ6od3g+An6Ai9mhGRvcYe8UJlH0prseN64M+6ZBBUoKYHZsitDP42gAJ8+eVWr3lw== + +kind-of@^6.0.3: + version "6.0.3" + resolved "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +klona@^2.0.4: + version "2.0.6" + resolved "https://registry.npmmirror.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22" + integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== + +less-loader@^10.2.0: + version "10.2.0" + resolved "https://registry.npmmirror.com/less-loader/-/less-loader-10.2.0.tgz#97286d8797dc3dc05b1d16b0ecec5f968bdd4e32" + integrity sha512-AV5KHWvCezW27GT90WATaDnfXBv99llDbtaj4bshq6DvAihMdNjaPDcUMa6EXKLRF+P2opFenJp89BXg91XLYg== + dependencies: + klona "^2.0.4" + +less@^4.1.2: + version "4.1.3" + resolved "https://registry.npmmirror.com/less/-/less-4.1.3.tgz#175be9ddcbf9b250173e0a00b4d6920a5b770246" + integrity sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA== + dependencies: + copy-anything "^2.0.1" + parse-node-version "^1.0.1" + tslib "^2.3.0" + optionalDependencies: + errno "^0.1.1" + graceful-fs "^4.1.2" + image-size "~0.5.0" + make-dir "^2.1.0" + mime "^1.4.1" + needle "^3.1.0" + source-map "~0.6.0" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +libnpmaccess@^6.0.4: + version "6.0.4" + resolved "https://registry.npmmirror.com/libnpmaccess/-/libnpmaccess-6.0.4.tgz#2dd158bd8a071817e2207d3b201d37cf1ad6ae6b" + integrity sha512-qZ3wcfIyUoW0+qSFkMBovcTrSGJ3ZeyvpR7d5N9pEYv/kXs8sHP2wiqEIXBKLFrZlmM0kR0RJD7mtfLngtlLag== + dependencies: + aproba "^2.0.0" + minipass "^3.1.1" + npm-package-arg "^9.0.1" + npm-registry-fetch "^13.0.0" + +libnpmdiff@^4.0.5: + version "4.0.5" + resolved "https://registry.npmmirror.com/libnpmdiff/-/libnpmdiff-4.0.5.tgz#ffaf93fa9440ea759444b8830fdb5c661b09a7c0" + integrity sha512-9fICQIzmH892UwHHPmb+Seup50UIBWcMIK2FdxvlXm9b4kc1nSH0b/BuY1mORJQtB6ydPMnn+BLzOTmd/SKJmw== + dependencies: + "@npmcli/disparity-colors" "^2.0.0" + "@npmcli/installed-package-contents" "^1.0.7" + binary-extensions "^2.2.0" + diff "^5.1.0" + minimatch "^5.0.1" + npm-package-arg "^9.0.1" + pacote "^13.6.1" + tar "^6.1.0" + +libnpmexec@^4.0.14: + version "4.0.14" + resolved "https://registry.npmmirror.com/libnpmexec/-/libnpmexec-4.0.14.tgz#9ad44232434b374e477eb2c2e4548baaf698f773" + integrity sha512-dwmzv2K29SdoAHBOa7QR6CfQbFG/PiZDRF6HZrlI6C4DLt2hNgOHTFaUGOpqE2C+YGu0ZwYTDywxRe0eOnf0ZA== + dependencies: + "@npmcli/arborist" "^5.6.3" + "@npmcli/ci-detect" "^2.0.0" + "@npmcli/fs" "^2.1.1" + "@npmcli/run-script" "^4.2.0" + chalk "^4.1.0" + mkdirp-infer-owner "^2.0.0" + npm-package-arg "^9.0.1" + npmlog "^6.0.2" + pacote "^13.6.1" + proc-log "^2.0.0" + read "^1.0.7" + read-package-json-fast "^2.0.2" + semver "^7.3.7" + walk-up-path "^1.0.0" + +libnpmfund@^3.0.5: + version "3.0.5" + resolved "https://registry.npmmirror.com/libnpmfund/-/libnpmfund-3.0.5.tgz#817f9e2120889beb483d9ba8eda142bb84293e4e" + integrity sha512-KdeRoG/dem8H3PcEU2/0SKi3ip7AWwczgS72y/3PE+PBrz/s/G52FNIA9jeLnBirkLC0sOyQHfeM3b7e24ZM+g== + dependencies: + "@npmcli/arborist" "^5.6.3" + +libnpmhook@^8.0.4: + version "8.0.4" + resolved "https://registry.npmmirror.com/libnpmhook/-/libnpmhook-8.0.4.tgz#6c58e5fe763ff5d600ae9c20457ea9a69d1f7d87" + integrity sha512-nuD6e+Nx0OprjEi0wOeqASMl6QIH235th/Du2/8upK3evByFhzIgdfOeP1OhstavW4xtsl0hk5Vw4fAWWuSUgA== + dependencies: + aproba "^2.0.0" + npm-registry-fetch "^13.0.0" + +libnpmorg@^4.0.4: + version "4.0.4" + resolved "https://registry.npmmirror.com/libnpmorg/-/libnpmorg-4.0.4.tgz#2a01d49372cf0df90d79a61e69bddaf2ed704311" + integrity sha512-1bTpD7iub1rDCsgiBguhJhiDufLQuc8DEti20euqsXz9O0ncXVpCYqf2SMmHR4GEdmAvAj2r7FMiyA9zGdaTpA== + dependencies: + aproba "^2.0.0" + npm-registry-fetch "^13.0.0" + +libnpmpack@^4.1.3: + version "4.1.3" + resolved "https://registry.npmmirror.com/libnpmpack/-/libnpmpack-4.1.3.tgz#025cfe39829acd8260662bf259e3a9331fc1e4b2" + integrity sha512-rYP4X++ME3ZiFO+2iN3YnXJ4LB4Gsd0z5cgszWJZxaEpDN4lRIXirSyynGNsN/hn4taqnlxD+3DPlFDShvRM8w== + dependencies: + "@npmcli/run-script" "^4.1.3" + npm-package-arg "^9.0.1" + pacote "^13.6.1" + +libnpmpublish@^6.0.5: + version "6.0.5" + resolved "https://registry.npmmirror.com/libnpmpublish/-/libnpmpublish-6.0.5.tgz#5a894f3de2e267d62f86be2a508e362599b5a4b1" + integrity sha512-LUR08JKSviZiqrYTDfywvtnsnxr+tOvBU0BF8H+9frt7HMvc6Qn6F8Ubm72g5hDTHbq8qupKfDvDAln2TVPvFg== + dependencies: + normalize-package-data "^4.0.0" + npm-package-arg "^9.0.1" + npm-registry-fetch "^13.0.0" + semver "^7.3.7" + ssri "^9.0.0" + +libnpmsearch@^5.0.4: + version "5.0.4" + resolved "https://registry.npmmirror.com/libnpmsearch/-/libnpmsearch-5.0.4.tgz#b32aa2b23051c00cdcc0912274d0d416e6655d81" + integrity sha512-XHDmsvpN5+pufvGnfLRqpy218gcGGbbbXR6wPrDJyd1em6agKdYByzU5ccskDHH9iVm2UeLydpDsW1ksYuU0cg== + dependencies: + npm-registry-fetch "^13.0.0" + +libnpmteam@^4.0.4: + version "4.0.4" + resolved "https://registry.npmmirror.com/libnpmteam/-/libnpmteam-4.0.4.tgz#ac26068808d93b1051d926457db14e4b3ff669ef" + integrity sha512-rzKSwi6MLzwwevbM/vl+BBQTErgn24tCfgPUdzBlszrw3j5necOu7WnTzgvZMDv6maGUwec6Ut1rxszOgH0l+Q== + dependencies: + aproba "^2.0.0" + npm-registry-fetch "^13.0.0" + +libnpmversion@^3.0.7: + version "3.0.7" + resolved "https://registry.npmmirror.com/libnpmversion/-/libnpmversion-3.0.7.tgz#e4c6c07ee28cf351ce1e2293a5ac9922b09ea94d" + integrity sha512-O0L4eNMUIMQ+effi1HsZPKp2N6wecwqGqB8PvkvmLPWN7EsdabdzAVG48nv0p/OjlbIai5KQg/L+qMMfCA4ZjA== + dependencies: + "@npmcli/git" "^3.0.0" + "@npmcli/run-script" "^4.1.3" + json-parse-even-better-errors "^2.3.1" + proc-log "^2.0.0" + semver "^7.3.7" + +lilconfig@2.0.5: + version "2.0.5" + resolved "https://registry.npmmirror.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25" + integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +lint-staged@^12.3.7: + version "12.5.0" + resolved "https://registry.npmmirror.com/lint-staged/-/lint-staged-12.5.0.tgz#d6925747480ae0e380d13988522f9dd8ef9126e3" + integrity sha512-BKLUjWDsKquV/JuIcoQW4MSAI3ggwEImF1+sB4zaKvyVx1wBk3FsG7UK9bpnmBTN1pm7EH2BBcMwINJzCRv12g== + dependencies: + cli-truncate "^3.1.0" + colorette "^2.0.16" + commander "^9.3.0" + debug "^4.3.4" + execa "^5.1.1" + lilconfig "2.0.5" + listr2 "^4.0.5" + micromatch "^4.0.5" + normalize-path "^3.0.0" + object-inspect "^1.12.2" + pidtree "^0.5.0" + string-argv "^0.3.1" + supports-color "^9.2.2" + yaml "^1.10.2" + +listr2@^4.0.5: + version "4.0.5" + resolved "https://registry.npmmirror.com/listr2/-/listr2-4.0.5.tgz#9dcc50221583e8b4c71c43f9c7dfd0ef546b75d5" + integrity sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA== + dependencies: + cli-truncate "^2.1.0" + colorette "^2.0.16" + log-update "^4.0.0" + p-map "^4.0.0" + rfdc "^1.3.0" + rxjs "^7.5.5" + through "^2.3.8" + wrap-ansi "^7.0.0" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash-es@^4.17.15, lodash-es@^4.17.21: + version "4.17.21" + resolved "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== + dependencies: + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" + +loose-envify@^1.0.0: + version "1.4.0" + resolved "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: + version "7.18.3" + resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" + integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== + +magic-string@^0.25.7: + version "0.25.9" + resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c" + integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ== + dependencies: + sourcemap-codec "^1.4.8" + +make-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.npmmirror.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +make-fetch-happen@^10.0.3, make-fetch-happen@^10.0.6, make-fetch-happen@^10.2.0: + version "10.2.1" + resolved "https://registry.npmmirror.com/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz#f5e3835c5e9817b617f2770870d9492d28678164" + integrity sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w== + dependencies: + agentkeepalive "^4.2.1" + cacache "^16.1.0" + http-cache-semantics "^4.1.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^7.7.1" + minipass "^3.1.6" + minipass-collect "^1.0.2" + minipass-fetch "^2.0.3" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.3" + promise-retry "^2.0.1" + socks-proxy-agent "^7.0.0" + ssri "^9.0.0" + +map-obj@^1.0.0: + version "1.0.1" + resolved "https://registry.npmmirror.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + integrity sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg== + +map-obj@^4.0.0: + version "4.3.0" + resolved "https://registry.npmmirror.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" + integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== + +meow@^8.0.0: + version "8.1.2" + resolved "https://registry.npmmirror.com/meow/-/meow-8.1.2.tgz#bcbe45bda0ee1729d350c03cffc8395a36c4e897" + integrity sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q== + dependencies: + "@types/minimist" "^1.2.0" + camelcase-keys "^6.2.2" + decamelize-keys "^1.1.0" + hard-rejection "^2.1.0" + minimist-options "4.1.0" + normalize-package-data "^3.0.0" + read-pkg-up "^7.0.1" + redent "^3.0.0" + trim-newlines "^3.0.0" + type-fest "^0.18.0" + yargs-parser "^20.2.3" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4, micromatch@^4.0.5: + version "4.0.5" + resolved "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@^1.4.1: + version "1.6.0" + resolved "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.npmmirror.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + +minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1, minimatch@^5.1.0: + version "5.1.6" + resolved "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimist-options@4.1.0: + version "4.1.0" + resolved "https://registry.npmmirror.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" + integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== + dependencies: + arrify "^1.0.1" + is-plain-obj "^1.1.0" + kind-of "^6.0.3" + +minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== + dependencies: + minipass "^3.0.0" + +minipass-fetch@^2.0.3: + version "2.1.2" + resolved "https://registry.npmmirror.com/minipass-fetch/-/minipass-fetch-2.1.2.tgz#95560b50c472d81a3bc76f20ede80eaed76d8add" + integrity sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA== + dependencies: + minipass "^3.1.6" + minipass-sized "^1.0.3" + minizlib "^2.1.2" + optionalDependencies: + encoding "^0.1.13" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.npmmirror.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-json-stream@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz#7edbb92588fbfc2ff1db2fc10397acb7b6b44aa7" + integrity sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg== + dependencies: + jsonparse "^1.3.1" + minipass "^3.0.0" + +minipass-pipeline@^1.2.4: + version "1.2.4" + resolved "https://registry.npmmirror.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass-sized@^1.0.3: + version "1.0.3" + resolved "https://registry.npmmirror.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" + integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== + dependencies: + minipass "^3.0.0" + +minipass@^3.0.0, minipass@^3.1.1, minipass@^3.1.6: + version "3.3.6" + resolved "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^4.0.0: + version "4.2.8" + resolved "https://registry.npmmirror.com/minipass/-/minipass-4.2.8.tgz#f0010f64393ecfc1d1ccb5f582bcaf45f48e1a3a" + integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ== + +minizlib@^2.1.1, minizlib@^2.1.2: + version "2.1.2" + resolved "https://registry.npmmirror.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mixpanel-browser@^2.45.0: + version "2.46.0" + resolved "https://registry.npmmirror.com/mixpanel-browser/-/mixpanel-browser-2.46.0.tgz#45cc829a432aa99bab24c0f36879001832424efd" + integrity sha512-qPFhAFMf5YzzGPd/sgoEl7G0N/ypJ6IaZmjkMYEPiW60i1Muo+v2Zl/dqRcCXwYfF5GvC5175muS1ZuaaYGXAQ== + +mkdirp-infer-owner@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz#55d3b368e7d89065c38f32fd38e638f0ab61d316" + integrity sha512-sdqtiFt3lkOaYvTXSRIUjkIdPTcxgv5+fgqYE/5qgwdw12cOrAuzzgzvVExIkH/ul1oeHN3bCLOWSG3XOqbKKw== + dependencies: + chownr "^2.0.0" + infer-owner "^1.0.4" + mkdirp "^1.0.3" + +mkdirp@^1.0.3, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.npmmirror.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +moment@^2.27.0: + version "2.29.4" + resolved "https://registry.npmmirror.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" + integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@^2.0.0, ms@^2.1.1, ms@^2.1.2: + version "2.1.3" + resolved "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +mustache@^4.2.0: + version "4.2.0" + resolved "https://registry.npmmirror.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64" + integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ== + +mute-stream@~0.0.4: + version "0.0.8" + resolved "https://registry.npmmirror.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +nanoid@^3.3.6: + version "3.3.6" + resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" + integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== + +nanoid@^4.0.0: + version "4.0.2" + resolved "https://registry.npmmirror.com/nanoid/-/nanoid-4.0.2.tgz#140b3c5003959adbebf521c170f282c5e7f9fb9e" + integrity sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw== + +nanopop@^2.1.0: + version "2.3.0" + resolved "https://registry.npmmirror.com/nanopop/-/nanopop-2.3.0.tgz#a5f672fba27d45d6ecbd0b59789c040072915123" + integrity sha512-fzN+T2K7/Ah25XU02MJkPZ5q4Tj5FpjmIYq4rvoHX4yb16HzFdCO6JxFFn5Y/oBhQ8no8fUZavnyIv9/+xkBBw== + +natural-compare-lite@^1.4.0: + version "1.4.0" + resolved "https://registry.npmmirror.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" + integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +needle@^3.1.0: + version "3.2.0" + resolved "https://registry.npmmirror.com/needle/-/needle-3.2.0.tgz#07d240ebcabfd65c76c03afae7f6defe6469df44" + integrity sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ== + dependencies: + debug "^3.2.6" + iconv-lite "^0.6.3" + sax "^1.2.4" + +negotiator@^0.6.3: + version "0.6.3" + resolved "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +node-gyp@^9.0.0, node-gyp@^9.1.0: + version "9.3.1" + resolved "https://registry.npmmirror.com/node-gyp/-/node-gyp-9.3.1.tgz#1e19f5f290afcc9c46973d68700cbd21a96192e4" + integrity sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg== + dependencies: + env-paths "^2.2.0" + glob "^7.1.4" + graceful-fs "^4.2.6" + make-fetch-happen "^10.0.3" + nopt "^6.0.0" + npmlog "^6.0.0" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.2" + which "^2.0.2" + +nopt@^6.0.0: + version "6.0.0" + resolved "https://registry.npmmirror.com/nopt/-/nopt-6.0.0.tgz#245801d8ebf409c6df22ab9d95b65e1309cdb16d" + integrity sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g== + dependencies: + abbrev "^1.0.0" + +normalize-package-data@^2.5.0: + version "2.5.0" + resolved "https://registry.npmmirror.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-package-data@^3.0.0: + version "3.0.3" + resolved "https://registry.npmmirror.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" + integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== + dependencies: + hosted-git-info "^4.0.1" + is-core-module "^2.5.0" + semver "^7.3.4" + validate-npm-package-license "^3.0.1" + +normalize-package-data@^4.0.0: + version "4.0.1" + resolved "https://registry.npmmirror.com/normalize-package-data/-/normalize-package-data-4.0.1.tgz#b46b24e0616d06cadf9d5718b29b6d445a82a62c" + integrity sha512-EBk5QKKuocMJhB3BILuKhmaPjI8vNRSpIfO9woLC6NyHVkKKdVEdAO1mrT0ZfxNR1lKwCcTkuZfmGIFdizZ8Pg== + dependencies: + hosted-git-info "^5.0.0" + is-core-module "^2.8.1" + semver "^7.3.5" + validate-npm-package-license "^3.0.4" + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-audit-report@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/npm-audit-report/-/npm-audit-report-3.0.0.tgz#1bf3e531208b5f77347c8d00c3d9badf5be30cd6" + integrity sha512-tWQzfbwz1sc4244Bx2BVELw0EmZlCsCF0X93RDcmmwhonCsPMoEviYsi+32R+mdRvOWXolPce9zo64n2xgPESw== + dependencies: + chalk "^4.0.0" + +npm-bundled@^1.1.1: + version "1.1.2" + resolved "https://registry.npmmirror.com/npm-bundled/-/npm-bundled-1.1.2.tgz#944c78789bd739035b70baa2ca5cc32b8d860bc1" + integrity sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ== + dependencies: + npm-normalize-package-bin "^1.0.1" + +npm-bundled@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/npm-bundled/-/npm-bundled-2.0.1.tgz#94113f7eb342cd7a67de1e789f896b04d2c600f4" + integrity sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw== + dependencies: + npm-normalize-package-bin "^2.0.0" + +npm-install-checks@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/npm-install-checks/-/npm-install-checks-5.0.0.tgz#5ff27d209a4e3542b8ac6b0c1db6063506248234" + integrity sha512-65lUsMI8ztHCxFz5ckCEC44DRvEGdZX5usQFriauxHEwt7upv1FKaQEmAtU0YnOAdwuNWCmk64xYiQABNrEyLA== + dependencies: + semver "^7.1.1" + +npm-normalize-package-bin@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" + integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== + +npm-normalize-package-bin@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz#9447a1adaaf89d8ad0abe24c6c84ad614a675fff" + integrity sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ== + +npm-package-arg@^9.0.0, npm-package-arg@^9.0.1, npm-package-arg@^9.1.0: + version "9.1.2" + resolved "https://registry.npmmirror.com/npm-package-arg/-/npm-package-arg-9.1.2.tgz#fc8acecb00235f42270dda446f36926ddd9ac2bc" + integrity sha512-pzd9rLEx4TfNJkovvlBSLGhq31gGu2QDexFPWT19yCDh0JgnRhlBLNo5759N0AJmBk+kQ9Y/hXoLnlgFD+ukmg== + dependencies: + hosted-git-info "^5.0.0" + proc-log "^2.0.1" + semver "^7.3.5" + validate-npm-package-name "^4.0.0" + +npm-packlist@^5.1.0: + version "5.1.3" + resolved "https://registry.npmmirror.com/npm-packlist/-/npm-packlist-5.1.3.tgz#69d253e6fd664b9058b85005905012e00e69274b" + integrity sha512-263/0NGrn32YFYi4J533qzrQ/krmmrWwhKkzwTuM4f/07ug51odoaNjUexxO4vxlzURHcmYMH1QjvHjsNDKLVg== + dependencies: + glob "^8.0.1" + ignore-walk "^5.0.1" + npm-bundled "^2.0.0" + npm-normalize-package-bin "^2.0.0" + +npm-pick-manifest@^7.0.0, npm-pick-manifest@^7.0.2: + version "7.0.2" + resolved "https://registry.npmmirror.com/npm-pick-manifest/-/npm-pick-manifest-7.0.2.tgz#1d372b4e7ea7c6712316c0e99388a73ed3496e84" + integrity sha512-gk37SyRmlIjvTfcYl6RzDbSmS9Y4TOBXfsPnoYqTHARNgWbyDiCSMLUpmALDj4jjcTZpURiEfsSHJj9k7EV4Rw== + dependencies: + npm-install-checks "^5.0.0" + npm-normalize-package-bin "^2.0.0" + npm-package-arg "^9.0.0" + semver "^7.3.5" + +npm-profile@^6.2.0: + version "6.2.1" + resolved "https://registry.npmmirror.com/npm-profile/-/npm-profile-6.2.1.tgz#975c31ec75a6ae029ab5b8820ffdcbae3a1e3d5e" + integrity sha512-Tlu13duByHyDd4Xy0PgroxzxnBYWbGGL5aZifNp8cx2DxUrHSoETXtPKg38aRPsBWMRfDtvcvVfJNasj7oImQQ== + dependencies: + npm-registry-fetch "^13.0.1" + proc-log "^2.0.0" + +npm-registry-fetch@^13.0.0, npm-registry-fetch@^13.0.1, npm-registry-fetch@^13.3.1: + version "13.3.1" + resolved "https://registry.npmmirror.com/npm-registry-fetch/-/npm-registry-fetch-13.3.1.tgz#bb078b5fa6c52774116ae501ba1af2a33166af7e" + integrity sha512-eukJPi++DKRTjSBRcDZSDDsGqRK3ehbxfFUcgaRd0Yp6kRwOwh2WVn0r+8rMB4nnuzvAk6rQVzl6K5CkYOmnvw== + dependencies: + make-fetch-happen "^10.0.6" + minipass "^3.1.6" + minipass-fetch "^2.0.3" + minipass-json-stream "^1.0.1" + minizlib "^2.1.2" + npm-package-arg "^9.0.1" + proc-log "^2.0.0" + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +npm-user-validate@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/npm-user-validate/-/npm-user-validate-1.0.1.tgz#31428fc5475fe8416023f178c0ab47935ad8c561" + integrity sha512-uQwcd/tY+h1jnEaze6cdX/LrhWhoBxfSknxentoqmIuStxUExxjWd3ULMLFPiFUrZKbOVMowH6Jq2FRWfmhcEw== + +npm@^8.13.1: + version "8.19.4" + resolved "https://registry.npmmirror.com/npm/-/npm-8.19.4.tgz#65ad6a2dfdd157a4ef4467fb86e8dcd35a43493f" + integrity sha512-3HANl8i9DKnUA89P4KEgVNN28EjSeDCmvEqbzOAuxCFDzdBZzjUl99zgnGpOUumvW5lvJo2HKcjrsc+tfyv1Hw== + dependencies: + "@isaacs/string-locale-compare" "^1.1.0" + "@npmcli/arborist" "^5.6.3" + "@npmcli/ci-detect" "^2.0.0" + "@npmcli/config" "^4.2.1" + "@npmcli/fs" "^2.1.0" + "@npmcli/map-workspaces" "^2.0.3" + "@npmcli/package-json" "^2.0.0" + "@npmcli/run-script" "^4.2.1" + abbrev "~1.1.1" + archy "~1.0.0" + cacache "^16.1.3" + chalk "^4.1.2" + chownr "^2.0.0" + cli-columns "^4.0.0" + cli-table3 "^0.6.2" + columnify "^1.6.0" + fastest-levenshtein "^1.0.12" + fs-minipass "^2.1.0" + glob "^8.0.1" + graceful-fs "^4.2.10" + hosted-git-info "^5.2.1" + ini "^3.0.1" + init-package-json "^3.0.2" + is-cidr "^4.0.2" + json-parse-even-better-errors "^2.3.1" + libnpmaccess "^6.0.4" + libnpmdiff "^4.0.5" + libnpmexec "^4.0.14" + libnpmfund "^3.0.5" + libnpmhook "^8.0.4" + libnpmorg "^4.0.4" + libnpmpack "^4.1.3" + libnpmpublish "^6.0.5" + libnpmsearch "^5.0.4" + libnpmteam "^4.0.4" + libnpmversion "^3.0.7" + make-fetch-happen "^10.2.0" + minimatch "^5.1.0" + minipass "^3.1.6" + minipass-pipeline "^1.2.4" + mkdirp "^1.0.4" + mkdirp-infer-owner "^2.0.0" + ms "^2.1.2" + node-gyp "^9.1.0" + nopt "^6.0.0" + npm-audit-report "^3.0.0" + npm-install-checks "^5.0.0" + npm-package-arg "^9.1.0" + npm-pick-manifest "^7.0.2" + npm-profile "^6.2.0" + npm-registry-fetch "^13.3.1" + npm-user-validate "^1.0.1" + npmlog "^6.0.2" + opener "^1.5.2" + p-map "^4.0.0" + pacote "^13.6.2" + parse-conflict-json "^2.0.2" + proc-log "^2.0.1" + qrcode-terminal "^0.12.0" + read "~1.0.7" + read-package-json "^5.0.2" + read-package-json-fast "^2.0.3" + readdir-scoped-modules "^1.1.0" + rimraf "^3.0.2" + semver "^7.3.7" + ssri "^9.0.1" + tar "^6.1.11" + text-table "~0.2.0" + tiny-relative-date "^1.3.0" + treeverse "^2.0.0" + validate-npm-package-name "^4.0.0" + which "^2.0.2" + write-file-atomic "^4.0.1" + +npmlog@^6.0.0, npmlog@^6.0.2: + version "6.0.2" + resolved "https://registry.npmmirror.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" + integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== + dependencies: + are-we-there-yet "^3.0.0" + console-control-strings "^1.1.0" + gauge "^4.0.3" + set-blocking "^2.0.0" + +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.12.2, object-inspect@^1.9.0: + version "1.12.3" + resolved "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" + integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== + +omit.js@^2.0.0: + version "2.0.2" + resolved "https://registry.npmmirror.com/omit.js/-/omit.js-2.0.2.tgz#dd9b8436fab947a5f3ff214cb2538631e313ec2f" + integrity sha512-hJmu9D+bNB40YpL9jYebQl4lsTW6yEHRTroJzNLqQJYHm7c+NQnJGfZmIWh8S3q3KoaxV1aLhV6B3+0N0/kyJg== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.npmmirror.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.0, onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +opener@^1.5.2: + version "1.5.2" + resolved "https://registry.npmmirror.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" + integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== + +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.npmmirror.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +pacote@^13.0.3, pacote@^13.6.1, pacote@^13.6.2: + version "13.6.2" + resolved "https://registry.npmmirror.com/pacote/-/pacote-13.6.2.tgz#0d444ba3618ab3e5cd330b451c22967bbd0ca48a" + integrity sha512-Gu8fU3GsvOPkak2CkbojR7vjs3k3P9cA6uazKTHdsdV0gpCEQq2opelnEv30KRQWgVzP5Vd/5umjcedma3MKtg== + dependencies: + "@npmcli/git" "^3.0.0" + "@npmcli/installed-package-contents" "^1.0.7" + "@npmcli/promise-spawn" "^3.0.0" + "@npmcli/run-script" "^4.1.0" + cacache "^16.0.0" + chownr "^2.0.0" + fs-minipass "^2.1.0" + infer-owner "^1.0.4" + minipass "^3.1.6" + mkdirp "^1.0.4" + npm-package-arg "^9.0.0" + npm-packlist "^5.1.0" + npm-pick-manifest "^7.0.0" + npm-registry-fetch "^13.0.1" + proc-log "^2.0.0" + promise-retry "^2.0.1" + read-package-json "^5.0.0" + read-package-json-fast "^2.0.3" + rimraf "^3.0.2" + ssri "^9.0.0" + tar "^6.1.11" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-conflict-json@^2.0.1, parse-conflict-json@^2.0.2: + version "2.0.2" + resolved "https://registry.npmmirror.com/parse-conflict-json/-/parse-conflict-json-2.0.2.tgz#3d05bc8ffe07d39600dc6436c6aefe382033d323" + integrity sha512-jDbRGb00TAPFsKWCpZZOT93SxVP9nONOSgES3AevqRq/CHvavEBvKAjxX9p5Y5F0RZLxH9Ufd9+RwtCsa+lFDA== + dependencies: + json-parse-even-better-errors "^2.3.1" + just-diff "^5.0.1" + just-diff-apply "^5.2.0" + +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse-node-version@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b" + integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pidtree@^0.5.0: + version "0.5.0" + resolved "https://registry.npmmirror.com/pidtree/-/pidtree-0.5.0.tgz#ad5fbc1de78b8a5f99d6fbdd4f6e4eee21d1aca1" + integrity sha512-9nxspIM7OpZuhBxPg73Zvyq7j1QMPMPsGKTqRc2XOaFQauDvoNz9fM1Wdkjmeo7l9GXOZiRs97sPkuayl39wjA== + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.9: + version "6.0.11" + resolved "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz#2e41dc39b7ad74046e1615185185cd0b17d0c8dc" + integrity sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss@^8.1.10, postcss@^8.4.13: + version "8.4.23" + resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.23.tgz#df0aee9ac7c5e53e1075c24a3613496f9e6552ab" + integrity sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA== + dependencies: + nanoid "^3.3.6" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^2.6.1: + version "2.8.8" + resolved "https://registry.npmmirror.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + +proc-log@^2.0.0, proc-log@^2.0.1: + version "2.0.1" + resolved "https://registry.npmmirror.com/proc-log/-/proc-log-2.0.1.tgz#8f3f69a1f608de27878f91f5c688b225391cb685" + integrity sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw== + +promise-all-reject-late@^1.0.0: + version "1.0.1" + resolved "https://registry.npmmirror.com/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz#f8ebf13483e5ca91ad809ccc2fcf25f26f8643c2" + integrity sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw== + +promise-call-limit@^1.0.1: + version "1.0.2" + resolved "https://registry.npmmirror.com/promise-call-limit/-/promise-call-limit-1.0.2.tgz#f64b8dd9ef7693c9c7613e7dfe8d6d24de3031ea" + integrity sha512-1vTUnfI2hzui8AEIixbdAJlFY4LFDXqQswy/2eOlThAscXCY4It8FdVuI0fMJGAB2aWGbdQf/gv0skKYXmdrHA== + +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== + +promise-retry@^2.0.1: + version "2.0.1" + resolved "https://registry.npmmirror.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" + integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== + dependencies: + err-code "^2.0.2" + retry "^0.12.0" + +promise@^7.0.1: + version "7.3.1" + resolved "https://registry.npmmirror.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== + dependencies: + asap "~2.0.3" + +promzard@^0.3.0: + version "0.3.0" + resolved "https://registry.npmmirror.com/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee" + integrity sha512-JZeYqd7UAcHCwI+sTOeUDYkvEU+1bQ7iE0UT1MgB/tERkAPkesW46MrpIySzODi+owTjZtiF8Ay5j9m60KmMBw== + dependencies: + read "1" + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" + integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== + +pug-attrs@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/pug-attrs/-/pug-attrs-3.0.0.tgz#b10451e0348165e31fad1cc23ebddd9dc7347c41" + integrity sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA== + dependencies: + constantinople "^4.0.1" + js-stringify "^1.0.2" + pug-runtime "^3.0.0" + +pug-code-gen@^3.0.2: + version "3.0.2" + resolved "https://registry.npmmirror.com/pug-code-gen/-/pug-code-gen-3.0.2.tgz#ad190f4943133bf186b60b80de483100e132e2ce" + integrity sha512-nJMhW16MbiGRiyR4miDTQMRWDgKplnHyeLvioEJYbk1RsPI3FuA3saEP8uwnTb2nTJEKBU90NFVWJBk4OU5qyg== + dependencies: + constantinople "^4.0.1" + doctypes "^1.1.0" + js-stringify "^1.0.2" + pug-attrs "^3.0.0" + pug-error "^2.0.0" + pug-runtime "^3.0.0" + void-elements "^3.1.0" + with "^7.0.0" + +pug-error@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/pug-error/-/pug-error-2.0.0.tgz#5c62173cb09c34de2a2ce04f17b8adfec74d8ca5" + integrity sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ== + +pug-filters@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/pug-filters/-/pug-filters-4.0.0.tgz#d3e49af5ba8472e9b7a66d980e707ce9d2cc9b5e" + integrity sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A== + dependencies: + constantinople "^4.0.1" + jstransformer "1.0.0" + pug-error "^2.0.0" + pug-walk "^2.0.0" + resolve "^1.15.1" + +pug-lexer@^5.0.1: + version "5.0.1" + resolved "https://registry.npmmirror.com/pug-lexer/-/pug-lexer-5.0.1.tgz#ae44628c5bef9b190b665683b288ca9024b8b0d5" + integrity sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w== + dependencies: + character-parser "^2.2.0" + is-expression "^4.0.0" + pug-error "^2.0.0" + +pug-linker@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/pug-linker/-/pug-linker-4.0.0.tgz#12cbc0594fc5a3e06b9fc59e6f93c146962a7708" + integrity sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw== + dependencies: + pug-error "^2.0.0" + pug-walk "^2.0.0" + +pug-load@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/pug-load/-/pug-load-3.0.0.tgz#9fd9cda52202b08adb11d25681fb9f34bd41b662" + integrity sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ== + dependencies: + object-assign "^4.1.1" + pug-walk "^2.0.0" + +pug-parser@^6.0.0: + version "6.0.0" + resolved "https://registry.npmmirror.com/pug-parser/-/pug-parser-6.0.0.tgz#a8fdc035863a95b2c1dc5ebf4ecf80b4e76a1260" + integrity sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw== + dependencies: + pug-error "^2.0.0" + token-stream "1.0.0" + +pug-runtime@^3.0.0, pug-runtime@^3.0.1: + version "3.0.1" + resolved "https://registry.npmmirror.com/pug-runtime/-/pug-runtime-3.0.1.tgz#f636976204723f35a8c5f6fad6acda2a191b83d7" + integrity sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg== + +pug-strip-comments@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz#f94b07fd6b495523330f490a7f554b4ff876303e" + integrity sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ== + dependencies: + pug-error "^2.0.0" + +pug-walk@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/pug-walk/-/pug-walk-2.0.0.tgz#417aabc29232bb4499b5b5069a2b2d2a24d5f5fe" + integrity sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ== + +pug@^3.0.2: + version "3.0.2" + resolved "https://registry.npmmirror.com/pug/-/pug-3.0.2.tgz#f35c7107343454e43bc27ae0ff76c731b78ea535" + integrity sha512-bp0I/hiK1D1vChHh6EfDxtndHji55XP/ZJKwsRqrz6lRia6ZC2OZbdAymlxdVFwd1L70ebrVJw4/eZ79skrIaw== + dependencies: + pug-code-gen "^3.0.2" + pug-filters "^4.0.0" + pug-lexer "^5.0.1" + pug-linker "^4.0.0" + pug-load "^3.0.0" + pug-parser "^6.0.0" + pug-runtime "^3.0.1" + pug-strip-comments "^2.0.0" + +punycode@^2.1.0: + version "2.3.0" + resolved "https://registry.npmmirror.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + +q@^1.5.1: + version "1.5.1" + resolved "https://registry.npmmirror.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== + +qrcode-terminal@^0.12.0: + version "0.12.0" + resolved "https://registry.npmmirror.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" + integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ== + +qs@^6.10.3: + version "6.11.1" + resolved "https://registry.npmmirror.com/qs/-/qs-6.11.1.tgz#6c29dff97f0c0060765911ba65cbc9764186109f" + integrity sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ== + dependencies: + side-channel "^1.0.4" + +query-string@^7.1.1: + version "7.1.3" + resolved "https://registry.npmmirror.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328" + integrity sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg== + dependencies: + decode-uri-component "^0.2.2" + filter-obj "^1.1.0" + split-on-first "^1.0.0" + strict-uri-encode "^2.0.0" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +quick-lru@^4.0.1: + version "4.0.1" + resolved "https://registry.npmmirror.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" + integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== + +raf@^3.4.1: + version "3.4.1" + resolved "https://registry.npmmirror.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== + dependencies: + performance-now "^2.1.0" + +read-cmd-shim@^3.0.0: + version "3.0.1" + resolved "https://registry.npmmirror.com/read-cmd-shim/-/read-cmd-shim-3.0.1.tgz#868c235ec59d1de2db69e11aec885bc095aea087" + integrity sha512-kEmDUoYf/CDy8yZbLTmhB1X9kkjf9Q80PCNsDMb7ufrGd6zZSQA1+UyjrO+pZm5K/S4OXCWJeiIt1JA8kAsa6g== + +read-package-json-fast@^2.0.2, read-package-json-fast@^2.0.3: + version "2.0.3" + resolved "https://registry.npmmirror.com/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz#323ca529630da82cb34b36cc0b996693c98c2b83" + integrity sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ== + dependencies: + json-parse-even-better-errors "^2.3.0" + npm-normalize-package-bin "^1.0.1" + +read-package-json@^5.0.0, read-package-json@^5.0.2: + version "5.0.2" + resolved "https://registry.npmmirror.com/read-package-json/-/read-package-json-5.0.2.tgz#b8779ccfd169f523b67208a89cc912e3f663f3fa" + integrity sha512-BSzugrt4kQ/Z0krro8zhTwV1Kd79ue25IhNN/VtHFy1mG/6Tluyi+msc0UpwaoQzxSHa28mntAjIZY6kEgfR9Q== + dependencies: + glob "^8.0.1" + json-parse-even-better-errors "^2.3.1" + normalize-package-data "^4.0.0" + npm-normalize-package-bin "^2.0.0" + +read-pkg-up@^7.0.1: + version "7.0.1" + resolved "https://registry.npmmirror.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" + integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== + dependencies: + find-up "^4.1.0" + read-pkg "^5.2.0" + type-fest "^0.8.1" + +read-pkg@^5.2.0: + version "5.2.0" + resolved "https://registry.npmmirror.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + +read@1, read@^1.0.7, read@~1.0.7: + version "1.0.7" + resolved "https://registry.npmmirror.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" + integrity sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ== + dependencies: + mute-stream "~0.0.4" + +readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdir-scoped-modules@^1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" + integrity sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw== + dependencies: + debuglog "^1.0.1" + dezalgo "^1.0.0" + graceful-fs "^4.1.2" + once "^1.3.0" + +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + +regenerator-runtime@^0.13.11: + version "0.13.11" + resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + +request-light@^0.5.4: + version "0.5.8" + resolved "https://registry.npmmirror.com/request-light/-/request-light-0.5.8.tgz#8bf73a07242b9e7b601fac2fa5dc22a094abcc27" + integrity sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resize-observer-polyfill@^1.5.1: + version "1.5.1" + resolved "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" + integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== + +resolve-from@5.0.0, resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-global@1.0.0, resolve-global@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/resolve-global/-/resolve-global-1.0.0.tgz#a2a79df4af2ca3f49bf77ef9ddacd322dad19255" + integrity sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw== + dependencies: + global-dirs "^0.1.1" + +resolve@^1.10.0, resolve@^1.15.1, resolve@^1.22.0: + version "1.22.2" + resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" + integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== + dependencies: + is-core-module "^2.11.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.npmmirror.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rfdc@^1.3.0: + version "1.3.0" + resolved "https://registry.npmmirror.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" + integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== + +rimraf@^3.0.0, rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +"rollup@>=2.59.0 <2.78.0": + version "2.77.3" + resolved "https://registry.npmmirror.com/rollup/-/rollup-2.77.3.tgz#8f00418d3a2740036e15deb653bed1a90ee0cc12" + integrity sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g== + optionalDependencies: + fsevents "~2.3.2" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +rxjs@^7.5.5: + version "7.8.1" + resolved "https://registry.npmmirror.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@^1.2.4: + version "1.2.4" + resolved "https://registry.npmmirror.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +screenfull@^6.0.1: + version "6.0.2" + resolved "https://registry.npmmirror.com/screenfull/-/screenfull-6.0.2.tgz#3dbe4b8c4f8f49fb8e33caa8f69d0bca730ab238" + integrity sha512-AQdy8s4WhNvUZ6P8F6PB21tSPIYKniic+Ogx0AacBMjKP1GUHN2E9URxQHtCusiwxudnCKkdy4GrHXPPJSkCCw== + +scroll-into-view-if-needed@^2.2.25: + version "2.2.31" + resolved "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz#d3c482959dc483e37962d1521254e3295d0d1587" + integrity sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA== + dependencies: + compute-scroll-into-view "^1.0.20" + +select@^1.1.2: + version "1.1.2" + resolved "https://registry.npmmirror.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" + integrity sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA== + +"semver@2 || 3 || 4 || 5", semver@^5.6.0: + version "5.7.1" + resolved "https://registry.npmmirror.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@7.3.7: + version "7.3.7" + resolved "https://registry.npmmirror.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + +semver@^7.0.0, semver@^7.1.1, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7: + version "7.5.0" + resolved "https://registry.npmmirror.com/semver/-/semver-7.5.0.tgz#ed8c5dc8efb6c629c88b23d41dc9bf40c1d96cd0" + integrity sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA== + dependencies: + lru-cache "^6.0.0" + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + +shallow-equal@^1.0.0: + version "1.2.1" + resolved "https://registry.npmmirror.com/shallow-equal/-/shallow-equal-1.2.1.tgz#4c16abfa56043aa20d050324efa68940b0da79da" + integrity sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.npmmirror.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +slice-ansi@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a" + integrity sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ== + dependencies: + ansi-styles "^6.0.0" + is-fullwidth-code-point "^4.0.0" + +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + +socks-proxy-agent@^7.0.0: + version "7.0.0" + resolved "https://registry.npmmirror.com/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz#dc069ecf34436621acb41e3efa66ca1b5fed15b6" + integrity sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww== + dependencies: + agent-base "^6.0.2" + debug "^4.3.3" + socks "^2.6.2" + +socks@^2.6.2: + version "2.7.1" + resolved "https://registry.npmmirror.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" + integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== + dependencies: + ip "^2.0.0" + smart-buffer "^4.2.0" + +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +source-map@0.6.1, source-map@^0.6.1, source-map@~0.6.0: + version "0.6.1" + resolved "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sourcemap-codec@^1.4.8: + version "1.4.8" + resolved "https://registry.npmmirror.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + +spdx-correct@^3.0.0: + version "3.2.0" + resolved "https://registry.npmmirror.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" + integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.npmmirror.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.npmmirror.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.13" + resolved "https://registry.npmmirror.com/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz#7189a474c46f8d47c7b0da4b987bb45e908bd2d5" + integrity sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w== + +split-on-first@^1.0.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" + integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== + +split2@^3.0.0: + version "3.2.2" + resolved "https://registry.npmmirror.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" + integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg== + dependencies: + readable-stream "^3.0.0" + +ssri@^9.0.0, ssri@^9.0.1: + version "9.0.1" + resolved "https://registry.npmmirror.com/ssri/-/ssri-9.0.1.tgz#544d4c357a8d7b71a19700074b6883fcb4eae057" + integrity sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q== + dependencies: + minipass "^3.1.1" + +strict-uri-encode@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" + integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== + +string-argv@^0.3.1: + version "0.3.1" + resolved "https://registry.npmmirror.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" + integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.0: + version "5.1.2" + resolved "https://registry.npmmirror.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.0.1" + resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" + integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== + dependencies: + ansi-regex "^6.0.1" + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^9.2.2: + version "9.3.1" + resolved "https://registry.npmmirror.com/supports-color/-/supports-color-9.3.1.tgz#34e4ad3c71c9a39dae3254ecc46c9b74e89e15a6" + integrity sha512-knBY82pjmnIzK3NifMo3RxEIRD9E0kIzV4BKcyTZ9+9kWgLMxd4PrsTSMoFQUabgRBbF8KOLRDCyKgNV+iK44Q== + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +tar@^6.1.0, tar@^6.1.11, tar@^6.1.2: + version "6.1.13" + resolved "https://registry.npmmirror.com/tar/-/tar-6.1.13.tgz#46e22529000f612180601a6fe0680e7da508847b" + integrity sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^4.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +text-extensions@^1.0.0: + version "1.9.0" + resolved "https://registry.npmmirror.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26" + integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ== + +text-table@^0.2.0, text-table@~0.2.0: + version "0.2.0" + resolved "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +three@^0.136.0: + version "0.136.0" + resolved "https://registry.npmmirror.com/three/-/three-0.136.0.tgz#b1504db021b46398ef468aa7849f3dcabb814f50" + integrity sha512-+fEMX7nYLz2ZesVP/dyifli5Jf8gR3XPAnFJveQ80aMhibFduzrADnjMbARXh8+W9qLK7rshJCjAIL/6cDxC+A== + +through2@^4.0.0: + version "4.0.2" + resolved "https://registry.npmmirror.com/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764" + integrity sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw== + dependencies: + readable-stream "3" + +"through@>=2.2.7 <3", through@^2.3.8: + version "2.3.8" + resolved "https://registry.npmmirror.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +tiny-emitter@^2.0.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" + integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== + +tiny-relative-date@^1.3.0: + version "1.3.0" + resolved "https://registry.npmmirror.com/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz#fa08aad501ed730f31cc043181d995c39a935e07" + integrity sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A== + +tinycolor2@^1.4.2: + version "1.6.0" + resolved "https://registry.npmmirror.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e" + integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +token-stream@1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/token-stream/-/token-stream-1.0.0.tgz#cc200eab2613f4166d27ff9afc7ca56d49df6eb4" + integrity sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg== + +treeverse@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/treeverse/-/treeverse-2.0.0.tgz#036dcef04bc3fd79a9b79a68d4da03e882d8a9ca" + integrity sha512-N5gJCkLu1aXccpOTtqV6ddSEi6ZmGkh3hjmbu1IjcavJK4qyOVQmi0myQKM7z5jVGmD68SJoliaVrMmVObhj6A== + +trim-newlines@^3.0.0: + version "3.0.1" + resolved "https://registry.npmmirror.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" + integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== + +ts-node@^10.8.1: + version "10.9.1" + resolved "https://registry.npmmirror.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" + integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.1.0, tslib@^2.3.0: + version "2.5.0" + resolved "https://registry.npmmirror.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" + integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.npmmirror.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.18.0: + version "0.18.1" + resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" + integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^0.6.0: + version "0.6.0" + resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +typescript@^4.4.3, typescript@^4.5.4: + version "4.9.5" + resolved "https://registry.npmmirror.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== + +ua-parser-js@^1.0.2: + version "1.0.35" + resolved "https://registry.npmmirror.com/ua-parser-js/-/ua-parser-js-1.0.35.tgz#c4ef44343bc3db0a3cbefdf21822f1b1fc1ab011" + integrity sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA== + +unique-filename@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/unique-filename/-/unique-filename-2.0.1.tgz#e785f8675a9a7589e0ac77e0b5c34d2eaeac6da2" + integrity sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A== + dependencies: + unique-slug "^3.0.0" + +unique-slug@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/unique-slug/-/unique-slug-3.0.0.tgz#6d347cf57c8a7a7a6044aabd0e2d74e4d76dc7c9" + integrity sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w== + dependencies: + imurmurhash "^0.1.4" + +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + +upath@^2.0.1: + version "2.0.1" + resolved "https://registry.npmmirror.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" + integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.1, util-deprecate@^1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.npmmirror.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4: + version "3.0.4" + resolved "https://registry.npmmirror.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +validate-npm-package-name@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz#fe8f1c50ac20afdb86f177da85b3600f0ac0d747" + integrity sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q== + dependencies: + builtins "^5.0.0" + +vite@^2.8.0: + version "2.9.15" + resolved "https://registry.npmmirror.com/vite/-/vite-2.9.15.tgz#2858dd5b2be26aa394a283e62324281892546f0b" + integrity sha512-fzMt2jK4vQ3yK56te3Kqpkaeq9DkcZfBbzHwYpobasvgYmP2SoAr6Aic05CsB4CzCZbsDv4sujX3pkEGhLabVQ== + dependencies: + esbuild "^0.14.27" + postcss "^8.4.13" + resolve "^1.22.0" + rollup ">=2.59.0 <2.78.0" + optionalDependencies: + fsevents "~2.3.2" + +void-elements@^3.1.0: + version "3.1.0" + resolved "https://registry.npmmirror.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" + integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== + +vscode-css-languageservice@^5.1.7: + version "5.4.2" + resolved "https://registry.npmmirror.com/vscode-css-languageservice/-/vscode-css-languageservice-5.4.2.tgz#69ea74c000bd653dfc8e458a1720d28b9ffa5cfb" + integrity sha512-DT7+7vfdT2HDNjDoXWtYJ0lVDdeDEdbMNdK4PKqUl2MS8g7PWt7J5G9B6k9lYox8nOfhCEjLnoNC3UKHHCR1lg== + dependencies: + vscode-languageserver-textdocument "^1.0.4" + vscode-languageserver-types "^3.16.0" + vscode-nls "^5.0.0" + vscode-uri "^3.0.3" + +vscode-html-languageservice@^4.1.0: + version "4.2.5" + resolved "https://registry.npmmirror.com/vscode-html-languageservice/-/vscode-html-languageservice-4.2.5.tgz#c0cc8ff3d824d16388bbac187e1828749eccf006" + integrity sha512-dbr10KHabB9EaK8lI0XZW7SqOsTfrNyT3Nuj0GoPi4LjGKUmMiLtsqzfedIzRTzqY+w0FiLdh0/kQrnQ0tLxrw== + dependencies: + vscode-languageserver-textdocument "^1.0.4" + vscode-languageserver-types "^3.16.0" + vscode-nls "^5.0.0" + vscode-uri "^3.0.3" + +vscode-json-languageservice@^4.1.8: + version "4.2.1" + resolved "https://registry.npmmirror.com/vscode-json-languageservice/-/vscode-json-languageservice-4.2.1.tgz#94b6f471ece193bf4a1ef37f6ab5cce86d50a8b4" + integrity sha512-xGmv9QIWs2H8obGbWg+sIPI/3/pFgj/5OWBhNzs00BkYQ9UaB2F6JJaGB/2/YOZJ3BvLXQTC4Q7muqU25QgAhA== + dependencies: + jsonc-parser "^3.0.0" + vscode-languageserver-textdocument "^1.0.3" + vscode-languageserver-types "^3.16.0" + vscode-nls "^5.0.0" + vscode-uri "^3.0.3" + +vscode-jsonrpc@8.1.0, vscode-jsonrpc@^8.0.0-next.2: + version "8.1.0" + resolved "https://registry.npmmirror.com/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0.tgz#cb9989c65e219e18533cc38e767611272d274c94" + integrity sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw== + +vscode-languageserver-protocol@3.17.3: + version "3.17.3" + resolved "https://registry.npmmirror.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3.tgz#6d0d54da093f0c0ee3060b81612cce0f11060d57" + integrity sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA== + dependencies: + vscode-jsonrpc "8.1.0" + vscode-languageserver-types "3.17.3" + +vscode-languageserver-textdocument@^1.0.1, vscode-languageserver-textdocument@^1.0.3, vscode-languageserver-textdocument@^1.0.4: + version "1.0.8" + resolved "https://registry.npmmirror.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.8.tgz#9eae94509cbd945ea44bca8dcfe4bb0c15bb3ac0" + integrity sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q== + +vscode-languageserver-types@3.17.3, vscode-languageserver-types@^3.15.1, vscode-languageserver-types@^3.16.0: + version "3.17.3" + resolved "https://registry.npmmirror.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz#72d05e47b73be93acb84d6e311b5786390f13f64" + integrity sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA== + +vscode-languageserver@^8.0.0-next.2: + version "8.1.0" + resolved "https://registry.npmmirror.com/vscode-languageserver/-/vscode-languageserver-8.1.0.tgz#5024253718915d84576ce6662dd46a791498d827" + integrity sha512-eUt8f1z2N2IEUDBsKaNapkz7jl5QpskN2Y0G01T/ItMxBxw1fJwvtySGB9QMecatne8jFIWJGWI61dWjyTLQsw== + dependencies: + vscode-languageserver-protocol "3.17.3" + +vscode-nls@^5.0.0: + version "5.2.0" + resolved "https://registry.npmmirror.com/vscode-nls/-/vscode-nls-5.2.0.tgz#3cb6893dd9bd695244d8a024bdf746eea665cc3f" + integrity sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng== + +vscode-pug-languageservice@0.29.8: + version "0.29.8" + resolved "https://registry.npmmirror.com/vscode-pug-languageservice/-/vscode-pug-languageservice-0.29.8.tgz#3d95f974bc273ddd8f5a8fb5d3ac8064b2dc63d1" + integrity sha512-QHYAzDSJLg7GOLxCZ12qsM0dAM0dPeMSS1t4kKfzLsfpErmZpFzkAIXbidVrNMdMffGZMtTuIlcpEyWHbx96Iw== + dependencies: + "@volar/code-gen" "0.29.8" + "@volar/shared" "0.29.8" + "@volar/source-map" "0.29.8" + "@volar/transforms" "0.29.8" + pug-lexer "^5.0.1" + pug-parser "^6.0.0" + vscode-languageserver "^8.0.0-next.2" + +vscode-typescript-languageservice@0.29.8: + version "0.29.8" + resolved "https://registry.npmmirror.com/vscode-typescript-languageservice/-/vscode-typescript-languageservice-0.29.8.tgz#370572e8c99c8b8190733a4bfc1b45c5f91aa044" + integrity sha512-eecDqHk4WjEvy6VHQ6teHczppQ9yJO2wExCy7yu7WiFj35qbw0h4G6Erv46MvP3ClL8FggFzD7s1qM6vdqJUfw== + dependencies: + "@volar/shared" "0.29.8" + semver "^7.3.5" + upath "^2.0.1" + vscode-languageserver "^8.0.0-next.2" + vscode-languageserver-textdocument "^1.0.1" + +vscode-uri@^2.1.2: + version "2.1.2" + resolved "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-2.1.2.tgz#c8d40de93eb57af31f3c715dd650e2ca2c096f1c" + integrity sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A== + +vscode-uri@^3.0.2, vscode-uri@^3.0.3: + version "3.0.7" + resolved "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.0.7.tgz#6d19fef387ee6b46c479e5fb00870e15e58c1eb8" + integrity sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA== + +vscode-vue-languageservice@0.29.8: + version "0.29.8" + resolved "https://registry.npmmirror.com/vscode-vue-languageservice/-/vscode-vue-languageservice-0.29.8.tgz#6d59aab4fb94215b99b6f7d0e2ab73babd398d05" + integrity sha512-qSJdvW5ttyGUB/8uWDKgo8vnIoFnXYlBP4Z/cn54btsRn6ZMw7IJGJU1381e7p/yGvMTLeGbugD53SghbnSa6g== + dependencies: + "@volar/code-gen" "0.29.8" + "@volar/html2pug" "0.29.8" + "@volar/shared" "0.29.8" + "@volar/source-map" "0.29.8" + "@volar/transforms" "0.29.8" + "@volar/vue-code-gen" "0.29.8" + "@vscode/emmet-helper" "^2.8.0" + "@vue/reactivity" "^3.2.21" + "@vue/shared" "^3.2.21" + request-light "^0.5.4" + upath "^2.0.1" + vscode-css-languageservice "^5.1.7" + vscode-html-languageservice "^4.1.0" + vscode-json-languageservice "^4.1.8" + vscode-languageserver "^8.0.0-next.2" + vscode-languageserver-textdocument "^1.0.1" + vscode-pug-languageservice "0.29.8" + vscode-typescript-languageservice "0.29.8" + +vue-clipboard2@^0.3.3: + version "0.3.3" + resolved "https://registry.npmmirror.com/vue-clipboard2/-/vue-clipboard2-0.3.3.tgz#331fec85f9d4f175eb0d4feaef4d77338562af36" + integrity sha512-aNWXIL2DKgJyY/1OOeITwAQz1fHaCIGvUFHf9h8UcoQBG5a74MkdhS/xqoYe7DNZdQmZRL+TAdIbtUs9OyVjbw== + dependencies: + clipboard "^2.0.0" + +vue-demi@*: + version "0.14.0" + resolved "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.0.tgz#dcfd9a9cf9bb62ada1582ec9042372cf67ca6190" + integrity sha512-gt58r2ogsNQeVoQ3EhoUAvUsH9xviydl0dWJj7dabBC/2L4uBId7ujtCwDRD0JhkGsV1i0CtfLAeyYKBht9oWg== + +vue-eslint-parser@^8.0.1: + version "8.3.0" + resolved "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz#5d31129a1b3dd89c0069ca0a1c88f970c360bd0d" + integrity sha512-dzHGG3+sYwSf6zFBa0Gi9ZDshD7+ad14DGOdTLjruRVgZXe2J+DcZ9iUhyR48z5g1PqRa20yt3Njna/veLJL/g== + dependencies: + debug "^4.3.2" + eslint-scope "^7.0.0" + eslint-visitor-keys "^3.1.0" + espree "^9.0.0" + esquery "^1.4.0" + lodash "^4.17.21" + semver "^7.3.5" + +vue-i18n@^9.1.9: + version "9.2.2" + resolved "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.2.2.tgz#aeb49d9424923c77e0d6441e3f21dafcecd0e666" + integrity sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ== + dependencies: + "@intlify/core-base" "9.2.2" + "@intlify/shared" "9.2.2" + "@intlify/vue-devtools" "9.2.2" + "@vue/devtools-api" "^6.2.1" + +vue-router@^4.0.14: + version "4.1.6" + resolved "https://registry.npmmirror.com/vue-router/-/vue-router-4.1.6.tgz#b70303737e12b4814578d21d68d21618469375a1" + integrity sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ== + dependencies: + "@vue/devtools-api" "^6.4.5" + +vue-tsc@^0.29.8: + version "0.29.8" + resolved "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-0.29.8.tgz#f4d8de5dd8756107c878489ccf9178d2d72fff47" + integrity sha512-pT0wLRjvRuSmB+J4WJT6uuV9mO0KtSSXEAtaVXZQzyk5+DJdbLIQTbRce/TXSkfqt1l1WogO78RjtOJFiMCgfQ== + dependencies: + "@volar/shared" "0.29.8" + vscode-vue-languageservice "0.29.8" + +vue-types@^3.0.0: + version "3.0.2" + resolved "https://registry.npmmirror.com/vue-types/-/vue-types-3.0.2.tgz#ec16e05d412c038262fc1efa4ceb9647e7fb601d" + integrity sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw== + dependencies: + is-plain-object "3.0.1" + +vue-types@^4.1.0: + version "4.2.1" + resolved "https://registry.npmmirror.com/vue-types/-/vue-types-4.2.1.tgz#f8f7e5fb42d4a6acda6d92c9736b510e5534c753" + integrity sha512-DNQZmJuOvovLUIp0BENRkdnZHbI0V4e2mNvjAZOAXKD56YGvRchtUYOXA/XqTxdv7Ng5SJLZqRKRpAhm5NLaPQ== + dependencies: + is-plain-object "5.0.0" + +vue3-angle@^0.1.6: + version "0.1.6" + resolved "https://registry.npmmirror.com/vue3-angle/-/vue3-angle-0.1.6.tgz#cd99ececdb175543537d5b058319347225949cab" + integrity sha512-XbGG4xWhbbbB4Ujj17wyy5GjfsvTQL8+P00eRj3Y2W239l1CvKGehemrr0f1+IJa+aJpHaPn4hB22ahLy67Iqw== + +vue3-colorpicker@^2.0.4: + version "2.1.2" + resolved "https://registry.npmmirror.com/vue3-colorpicker/-/vue3-colorpicker-2.1.2.tgz#fefb29040d9c0bf63a4f9c56553415dbd305325c" + integrity sha512-ojCug0yVj5KOxoQo4GjhLXLsQ3P+y9UdNBKWD+ZRbeuswfdVVGsiL21BmIw+fVuLmrXJ1KC2y8nyskjsViBHRA== + dependencies: + "@aesoper/normal-utils" "^0.1.5" + "@popperjs/core" "^2.10.1" + "@vueuse/core" "^6.5.3" + gradient-parser "^1.0.2" + lodash-es "^4.17.21" + tinycolor2 "^1.4.2" + vue-types "^4.1.0" + vue3-angle "^0.1.6" + vue3-normal-library "^0.1.6" + +vue3-normal-library@^0.1.6: + version "0.1.6" + resolved "https://registry.npmmirror.com/vue3-normal-library/-/vue3-normal-library-0.1.6.tgz#f8731f861e325ebe39ab3df5af934d76b6998fe7" + integrity sha512-TSqCeD092ETnjqamNKtXencLnG4a+NVWFZgalmyPtFH1FHvpxLP7eptT8krOL2sZVspficic8DghfDakw3tKRQ== + dependencies: + lodash-es "^4.17.21" + raf "^3.4.1" + vue "^3.2.6" + vue-types "^4.1.0" + +vue@^3.2.25, vue@^3.2.6: + version "3.2.47" + resolved "https://registry.npmmirror.com/vue/-/vue-3.2.47.tgz#3eb736cbc606fc87038dbba6a154707c8a34cff0" + integrity sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ== + dependencies: + "@vue/compiler-dom" "3.2.47" + "@vue/compiler-sfc" "3.2.47" + "@vue/runtime-dom" "3.2.47" + "@vue/server-renderer" "3.2.47" + "@vue/shared" "3.2.47" + +walk-up-path@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/walk-up-path/-/walk-up-path-1.0.0.tgz#d4745e893dd5fd0dbb58dd0a4c6a33d9c9fec53e" + integrity sha512-hwj/qMDUEjCU5h0xr90KGCf0tg0/LgJbmOWgrWKYlcJZM7XvquvUJZ0G/HMGr7F7OQMOUuPHWP9JpriinkAlkg== + +warning@^4.0.0: + version "4.0.3" + resolved "https://registry.npmmirror.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + +wcwidth@^1.0.0: + version "1.0.1" + resolved "https://registry.npmmirror.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== + dependencies: + defaults "^1.0.3" + +which@^2.0.1, which@^2.0.2: + version "2.0.2" + resolved "https://registry.npmmirror.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.5: + version "1.1.5" + resolved "https://registry.npmmirror.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + +with@^7.0.0: + version "7.0.2" + resolved "https://registry.npmmirror.com/with/-/with-7.0.2.tgz#ccee3ad542d25538a7a7a80aad212b9828495bac" + integrity sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w== + dependencies: + "@babel/parser" "^7.9.6" + "@babel/types" "^7.9.6" + assert-never "^1.2.1" + babel-walk "3.0.0-canary-5" + +word-wrap@^1.2.3: + version "1.2.3" + resolved "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.0, write-file-atomic@^4.0.1: + version "4.0.2" + resolved "https://registry.npmmirror.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^1.10.0, yaml@^1.10.2: + version "1.10.2" + resolved "https://registry.npmmirror.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +yargs-parser@^20.2.3: + version "20.2.9" + resolved "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.0.0: + version "17.7.1" + resolved "https://registry.npmmirror.com/yargs/-/yargs-17.7.1.tgz#34a77645201d1a8fc5213ace787c220eabbd0967" + integrity sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.npmmirror.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From 10ca65abfc9ce4dc0676276a98b7e03b9d5c4789 Mon Sep 17 00:00:00 2001 From: Jagger Wang Date: Thu, 27 Apr 2023 15:25:01 +0800 Subject: [PATCH 17/37] CI support text tool. Signed-off-by: Jagger Wang --- .ops/alidev/frontend-deployment.yml | 24 ++++++++++++++++++++++++ .ops/alitest/frontend-deployment.yml | 24 ++++++++++++++++++++++++ deploy/nginx/conf.d/default.conf | 16 ++++++++++++++++ frontend/.dockerignore | 3 ++- frontend/Dockerfile | 2 ++ frontend/README.md | 8 +++++--- 6 files changed, 73 insertions(+), 4 deletions(-) diff --git a/.ops/alidev/frontend-deployment.yml b/.ops/alidev/frontend-deployment.yml index 36097923..c2b181b6 100644 --- a/.ops/alidev/frontend-deployment.yml +++ b/.ops/alidev/frontend-deployment.yml @@ -119,3 +119,27 @@ spec: - hosts: - xtreme1.alidev.beisai.com secretName: alidev.beisai.com +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + nginx.ingress.kubernetes.io/rewrite-target: /text-tool/$2 + name: frontend-text-tool +spec: + ingressClassName: nginx + rules: + - host: xtreme1.alidev.beisai.com + http: + paths: + - backend: + service: + name: frontend + port: + number: 80 + path: /tool/text(/|$)(.*) + pathType: Prefix + tls: + - hosts: + - xtreme1.alidev.beisai.com + secretName: alidev.beisai.com diff --git a/.ops/alitest/frontend-deployment.yml b/.ops/alitest/frontend-deployment.yml index 8e217791..d17a8a30 100644 --- a/.ops/alitest/frontend-deployment.yml +++ b/.ops/alitest/frontend-deployment.yml @@ -119,3 +119,27 @@ spec: - hosts: - xtreme1.alitest.beisai.com secretName: alitest.beisai.com +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + nginx.ingress.kubernetes.io/rewrite-target: /text-tool/$2 + name: frontend-text-tool +spec: + ingressClassName: nginx + rules: + - host: xtreme1.alitest.beisai.com + http: + paths: + - backend: + service: + name: frontend + port: + number: 80 + path: /tool/text(/|$)(.*) + pathType: Prefix + tls: + - hosts: + - xtreme1.alitest.beisai.com + secretName: alitest.beisai.com diff --git a/deploy/nginx/conf.d/default.conf b/deploy/nginx/conf.d/default.conf index 85741c61..7c4b2517 100644 --- a/deploy/nginx/conf.d/default.conf +++ b/deploy/nginx/conf.d/default.conf @@ -55,6 +55,22 @@ server { proxy_pass http://frontend:80/pc-tool/; } + location /tool/text { + set $no_cache 0; + if ($uri ~* ^/tool/text[/]?$) { + set $no_cache 1; + } + if ($uri ~* \.(?:html|json)$) { + set $no_cache 1; + } + if ($no_cache = 1) { + add_header Cache-Control "no-store,no-cache"; + add_header Pragma "no-cache"; + } + + proxy_pass http://frontend:80/text-tool/; + } + location /api/ { proxy_set_header Host $http_host; proxy_set_header X-Forwarded-Proto $scheme; diff --git a/frontend/.dockerignore b/frontend/.dockerignore index 314420ef..422f29e0 100644 --- a/frontend/.dockerignore +++ b/frontend/.dockerignore @@ -2,4 +2,5 @@ dist/ main/node_modules/ pc-tool/node_modules/ -image-tool/node_modules/ \ No newline at end of file +image-tool/node_modules/ +text-tool/node_modules/ \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 697d23bb..6a5379d7 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -8,6 +8,8 @@ RUN --mount=type=cache,target=image-tool/node_modules \ cd image-tool && npm install && npm run build RUN --mount=type=cache,target=pc-tool/node_modules \ cd pc-tool && npm install && npm run build +RUN --mount=type=cache,target=text-tool/node_modules \ + cd text-tool && npm install && npm run build FROM nginx:1.22 COPY --from=build /build/dist /usr/share/nginx/html diff --git a/frontend/README.md b/frontend/README.md index 41510667..4d1ed54c 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -6,15 +6,17 @@ Xtreme1 frontend mainly includes three parts, each part is a separate project an - [main](./main/README.md) - Main web application - [pc-tool](./pc-tool/README.md) - Point cloud annotation tool - [image-tool](./image-tool/README.md) - Image annotation tool +- [text-tool](./text-tool/README.md) - Text annotation tool ## Deploy Each part is developed independently, and finally the static files are put together to `frontend/dist`. -`main` build to `/main` -- nginx: `/` -`pc-tool` build to `/pc-tool` -- nginx: `/tool/pc` -`image-tool` build to `/image-tool` -- nginx: `/tool/image` +`main` build to `/main` -- nginx: `/` +`pc-tool` build to `/pc-tool` -- nginx: `/tool/pc` +`image-tool` build to `/image-tool` -- nginx: `/tool/image` +`text-tool` build to `/text-tool` -- nginx: `/tool/text` For more information, refer to `.ops/**/frontend-deployment.yml` From fb2d377b544743b6459222158f20aa9f24be58a3 Mon Sep 17 00:00:00 2001 From: Jagger Wang Date: Thu, 27 Apr 2023 15:47:30 +0800 Subject: [PATCH 18/37] CI build text tool. Signed-off-by: Jagger Wang --- .ops/.gitlab-ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.ops/.gitlab-ci.yml b/.ops/.gitlab-ci.yml index 0e8978c4..a17eb962 100644 --- a/.ops/.gitlab-ci.yml +++ b/.ops/.gitlab-ci.yml @@ -65,6 +65,9 @@ build-frontend-package: - cd ../pc-tool - npm install - npm run build + - cd ../text-tool + - npm install + - npm run build artifacts: paths: - frontend/$FRONTEND_PACKAGE_DIR @@ -73,6 +76,7 @@ build-frontend-package: - frontend/main/node_modules - frontend/image-tool/node_modules - frontend/pc-tool/node_modules + - frontend/text-tool/node_modules build-release-package: stage: build-package From 009919ddf8f2374e0bd3d876a8a60bcceff5459d Mon Sep 17 00:00:00 2001 From: raincolor <1037428213@qq.com> Date: Thu, 27 Apr 2023 15:48:19 +0800 Subject: [PATCH 19/37] feat: add text card --- .../components/TextToolCard/index.vue | 327 ++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 frontend/main/src/views/texttool/components/TextToolCard/index.vue diff --git a/frontend/main/src/views/texttool/components/TextToolCard/index.vue b/frontend/main/src/views/texttool/components/TextToolCard/index.vue new file mode 100644 index 00000000..b1f45a0e --- /dev/null +++ b/frontend/main/src/views/texttool/components/TextToolCard/index.vue @@ -0,0 +1,327 @@ + + + + + From ac47b9d125d890b944ebf030917e343ae43f181a Mon Sep 17 00:00:00 2001 From: raincolor <1037428213@qq.com> Date: Thu, 27 Apr 2023 16:12:36 +0800 Subject: [PATCH 20/37] feat: add text-tool build config Signed-off-by: raincolor <1037428213@qq.com> --- frontend/text-tool/vite.config.build.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/text-tool/vite.config.build.ts b/frontend/text-tool/vite.config.build.ts index 0949d9ad..ba044754 100644 --- a/frontend/text-tool/vite.config.build.ts +++ b/frontend/text-tool/vite.config.build.ts @@ -3,8 +3,8 @@ const baseConfig = require('./vite.config'); // https://vitejs.dev/config/ module.exports = mergeConfig(baseConfig, { - base: '/tool/pc/', + base: '/tool/text/', build: { - outDir: '../dist/pc-tool', + outDir: '../dist/text-tool', }, }); From a646e8ead3619a5bd240297fd2533cfbab948f60 Mon Sep 17 00:00:00 2001 From: raincolor <1037428213@qq.com> Date: Thu, 27 Apr 2023 16:29:12 +0800 Subject: [PATCH 21/37] feat: add thumbsup img and Main application jumps to text-tool Signed-off-by: raincolor <1037428213@qq.com> --- frontend/main/src/utils/business/index.ts | 2 +- frontend/text-tool/public/img/had-thumbs-down.png | Bin 0 -> 375 bytes frontend/text-tool/public/img/had-thumbs-up.png | Bin 0 -> 374 bytes frontend/text-tool/public/img/thumbs-down.png | Bin 0 -> 353 bytes frontend/text-tool/public/img/thumbs-up.png | Bin 0 -> 347 bytes 5 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 frontend/text-tool/public/img/had-thumbs-down.png create mode 100644 frontend/text-tool/public/img/had-thumbs-up.png create mode 100644 frontend/text-tool/public/img/thumbs-down.png create mode 100644 frontend/text-tool/public/img/thumbs-up.png diff --git a/frontend/main/src/utils/business/index.ts b/frontend/main/src/utils/business/index.ts index 97cb55b3..86bcda01 100644 --- a/frontend/main/src/utils/business/index.ts +++ b/frontend/main/src/utils/business/index.ts @@ -22,7 +22,7 @@ export const goToTool = (query: any, type?: datasetTypeEnum) => { toolPath = 'http://localhost:3300/tool/image'; break; case datasetTypeEnum.TEXT: - toolPath = '/#/texttool'; + toolPath = 'http://localhost:3300/tool/text'; break; default: toolPath = 'http://localhost:3200/tool/pc'; diff --git a/frontend/text-tool/public/img/had-thumbs-down.png b/frontend/text-tool/public/img/had-thumbs-down.png new file mode 100644 index 0000000000000000000000000000000000000000..bc609b3d9036e9bf4bdbd91b05cc7740c9a3815a GIT binary patch literal 375 zcmeAS@N?(olHy`uVBq!ia0vp@Ak4uAB#T}@sR2@)1s;*b3=G`DAk4@xYmNj^kg+(( z-HBn{IhmJ04okYDuOkD)#(wTUiL5|59#0p?kP61BL3e!*JMe6q+x|prN(k$HTfezu%mn&!3pkm!Ev( z0jq$}UN?KjiJJsx#C-@(d{y($jD_pbnR~W5zxVxBR$ulr;$uk+-JeB6JFDBjRW5NZ+G%6m-d;g<`-4XJwmmkRcHyiVQr z^4#5fp>K}+e@nT^Z)T@fpU*hC?#=;yHH{ay9XdOjUfuVyYuNEwiSgtI?^?#s3h5Sm TrBVxk;l$wS>gTe~DWM4fKWLaU literal 0 HcmV?d00001 diff --git a/frontend/text-tool/public/img/had-thumbs-up.png b/frontend/text-tool/public/img/had-thumbs-up.png new file mode 100644 index 0000000000000000000000000000000000000000..6fee34aa87567f8501c217f937eb7c6f9df86456 GIT binary patch literal 374 zcmeAS@N?(olHy`uVBq!ia0vp@Ak4uAB#T}@sR2@)1s;*b3=G`DAk4@xYmNj^kg+(( z-HBn{IhmJ04okYDuOkD)#(wTUiL5|5Zci7-kP61BLDqhU9R%iXFMHUx#znrk#Y0d? z^dzT8o@rHZ^2ldnCqGn^dQ~Oh&cuTv>|U*^>8r zQ@&0<_)|dBd?|ang-874vcvU?UgA3!^d%f}Q@{A{P34oQmnxqW)Q$w)J=J{Uv(~PS zm!o&cnoMIiTV}QI&OM)5{|c8mTmRFpR%PDOYG@uopD6Opa)h z&2XKzKsTcCp2rUsD*^2WWlsD13Nv^edG%+`J$Y{5+?m@?-{i@hH7ob_Ji#afXRoKR z*WbooKkG6{g@0xX%j@6&URa!qy~ls}LXgPQmV)DtEaW^sOxNhD`NnK9d9ko_qILtH zp6_Luu!#-UXX0IjoKog|eycSp_qJ+f-$Sk+J$qHpC8ow+cR&C6Q(#?C&IGwopEISR zt5)T$TJ>vB`?}ZrmSt{N;3!j&o^Z3}zJKMeoQ=GbBMO8kim2TNh5&=7tDnm{r-UW|0?CWr literal 0 HcmV?d00001 diff --git a/frontend/text-tool/public/img/thumbs-up.png b/frontend/text-tool/public/img/thumbs-up.png new file mode 100644 index 0000000000000000000000000000000000000000..e24f29bc99e07de03efbc04ac61d2932050a8227 GIT binary patch literal 347 zcmeAS@N?(olHy`uVBq!ia0vp@Ak4uAB#T}@sR2@)1s;*b3=G`DAk4@xYmNj^kg+(( z-HBn{IhmJ04okYDuOkD)#(wTUiL5~W2TvErkP61BL3eqZ3J8Qc{VRcthc zUvTVd)V63;KEbxB@z)V%fjbU&7~+4fzAu@)LC{w}bN$=$`0DRpC8Rf)_!jfbOugxM zltp@5=j&a2Uhm4Br1IG=;g5pnEQ{x=cYeJ0_Q+ISx@YgUu-NO6*k(V^j3=67 z|Z=h5G;i literal 0 HcmV?d00001 From a250ce09db66ac3299465af3c5c44744ce48b9bf Mon Sep 17 00:00:00 2001 From: fanyinbo <1553199396@qq.com> Date: Thu, 27 Apr 2023 16:34:29 +0800 Subject: [PATCH 22/37] Update init sql Signed-off-by: fanyinbo <1553199396@qq.com> --- deploy/mysql/migration/V1__Create_tables.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/mysql/migration/V1__Create_tables.sql b/deploy/mysql/migration/V1__Create_tables.sql index 72aad6f7..cc009ffc 100644 --- a/deploy/mysql/migration/V1__Create_tables.sql +++ b/deploy/mysql/migration/V1__Create_tables.sql @@ -424,7 +424,7 @@ CREATE TABLE `model_run_record` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Primary key', `model_id` bigint(20) NOT NULL COMMENT 'Model id', - `model_version` varchar(255) NOT NULL COMMENT 'Model version', + `model_version` varchar(255) DEFAULT NULL COMMENT 'Model version', `run_no` varchar(20) NOT NULL COMMENT 'Serial number(For interface display)', `dataset_id` bigint(20) NOT NULL COMMENT 'Dataset id', `status` enum('STARTED','RUNNING','SUCCESS','FAILURE','SUCCESS_WITH_ERROR') CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT 'Model running status', From a0c4986f49d66cbfc60c010934fbed9da18287fb Mon Sep 17 00:00:00 2001 From: Zhangfc846072 Date: Thu, 27 Apr 2023 16:48:20 +0800 Subject: [PATCH 23/37] fix: data && style --- .../src/components/Editor/text-item.vue | 62 +++++++++++++++ .../src/components/Editor/text-main.vue | 78 ++++++++++++++++++- .../packages/pc-editor/common/DataManager.ts | 23 +++++- .../packages/pc-editor/common/LoadManager.ts | 3 +- .../src/packages/pc-editor/config/event.ts | 9 +++ .../text-tool/src/packages/pc-editor/type.ts | 13 +++- 6 files changed, 179 insertions(+), 9 deletions(-) create mode 100644 frontend/text-tool/src/components/Editor/text-item.vue diff --git a/frontend/text-tool/src/components/Editor/text-item.vue b/frontend/text-tool/src/components/Editor/text-item.vue new file mode 100644 index 00000000..9b3656d7 --- /dev/null +++ b/frontend/text-tool/src/components/Editor/text-item.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/frontend/text-tool/src/components/Editor/text-main.vue b/frontend/text-tool/src/components/Editor/text-main.vue index 628096a6..d419ec3b 100644 --- a/frontend/text-tool/src/components/Editor/text-main.vue +++ b/frontend/text-tool/src/components/Editor/text-main.vue @@ -1,7 +1,79 @@ - + + + diff --git a/frontend/text-tool/src/packages/pc-editor/common/DataManager.ts b/frontend/text-tool/src/packages/pc-editor/common/DataManager.ts index df3fe9b4..17e25e50 100644 --- a/frontend/text-tool/src/packages/pc-editor/common/DataManager.ts +++ b/frontend/text-tool/src/packages/pc-editor/common/DataManager.ts @@ -64,12 +64,31 @@ export default class DataManager { setJSONData(data: ITextItem[]) { this.clearTextMap(); data.forEach((e) => { - this.textMap.set(e.id, e); + let initItem: ITextItem = { + ...e, + uuid: '', + direction: '' + } + this.textMap.set(e.id, initItem); }); } getTextById(id: string) { - this.textMap.get(id); + return this.textMap.get(id); + } + updateTextDataState(data: Record) { + let curFrame = this.editor.getCurrentFrame(); + let frameData = data[curFrame.id]; + if (!frameData || frameData.length == 0) return; + frameData.forEach((e: any) => { + let item = this.getTextById(e.messageId); + if (!item) return; + item.uuid = e.id; + item.direction = e.direction; + item.type = e.type; + item.createdAt = e.createdAt; + item.createdBy = e.createdBy; + }); } clearTextMap() { this.textMap.clear(); diff --git a/frontend/text-tool/src/packages/pc-editor/common/LoadManager.ts b/frontend/text-tool/src/packages/pc-editor/common/LoadManager.ts index 4f8dc020..59a8a6bc 100644 --- a/frontend/text-tool/src/packages/pc-editor/common/LoadManager.ts +++ b/frontend/text-tool/src/packages/pc-editor/common/LoadManager.ts @@ -67,6 +67,8 @@ export default class LoadManager { try { // let data = await api.getDataObject(datInfo.dataId); let data = await this.editor.businessManager.getFrameObject(frame); + this.editor.dataManager.updateTextDataState(data.objectsMap as any); + this.editor.dispatchEvent({ type: Event.ANNOTATE_LOADED}); // frame.queryTime = data.queryTime; // this.setTrackData(data.objectsMap); @@ -209,7 +211,6 @@ export default class LoadManager { } setResource(data: ITextItem[]) { - console.log('=======setResource:', data); this.editor.dataManager.setJSONData(data); } } diff --git a/frontend/text-tool/src/packages/pc-editor/config/event.ts b/frontend/text-tool/src/packages/pc-editor/config/event.ts index 95d36a55..4b87611f 100644 --- a/frontend/text-tool/src/packages/pc-editor/config/event.ts +++ b/frontend/text-tool/src/packages/pc-editor/config/event.ts @@ -1,8 +1,17 @@ const Event = { + // text tool + // common + // load + ANNOTATE_LOADED: 'annotate_loaded', + + + UNDO: 'undo', REDO: 'redo', RESET: 'reset', EXECUTE: 'execute', + + SHOW_CLASS_INFO: 'show_class_info', POINTS_CHANGE: 'points_change', diff --git a/frontend/text-tool/src/packages/pc-editor/type.ts b/frontend/text-tool/src/packages/pc-editor/type.ts index 7c8b29e2..6216338f 100644 --- a/frontend/text-tool/src/packages/pc-editor/type.ts +++ b/frontend/text-tool/src/packages/pc-editor/type.ts @@ -333,11 +333,18 @@ export interface IFrame { } export interface ITextItem { - id: string; + id: string; // messageid + uuid: string; role: string; text: string; - // 点赞 - // 踩 + direction: 'up' | 'down' | ''; + parentId?: string; + meta?: any; + type?: string; + + version?: string; + createdBy?: any; + createdAt?: string; } export interface IAnnotationItem { From 97c904ec25284a45d5d94f58e3da434d79987f5f Mon Sep 17 00:00:00 2001 From: raincolor <1037428213@qq.com> Date: Thu, 27 Apr 2023 17:36:03 +0800 Subject: [PATCH 24/37] feat: text-tool updated --- .../assets}/img/had-thumbs-down.png | Bin .../assets}/img/had-thumbs-up.png | Bin .../assets}/img/thumbs-down.png | Bin .../{public => src/assets}/img/thumbs-up.png | Bin .../src/components/Editor/text-item.vue | 164 ++++++++++++------ .../src/components/Editor/text-main.vue | 49 +----- 6 files changed, 118 insertions(+), 95 deletions(-) rename frontend/text-tool/{public => src/assets}/img/had-thumbs-down.png (100%) rename frontend/text-tool/{public => src/assets}/img/had-thumbs-up.png (100%) rename frontend/text-tool/{public => src/assets}/img/thumbs-down.png (100%) rename frontend/text-tool/{public => src/assets}/img/thumbs-up.png (100%) diff --git a/frontend/text-tool/public/img/had-thumbs-down.png b/frontend/text-tool/src/assets/img/had-thumbs-down.png similarity index 100% rename from frontend/text-tool/public/img/had-thumbs-down.png rename to frontend/text-tool/src/assets/img/had-thumbs-down.png diff --git a/frontend/text-tool/public/img/had-thumbs-up.png b/frontend/text-tool/src/assets/img/had-thumbs-up.png similarity index 100% rename from frontend/text-tool/public/img/had-thumbs-up.png rename to frontend/text-tool/src/assets/img/had-thumbs-up.png diff --git a/frontend/text-tool/public/img/thumbs-down.png b/frontend/text-tool/src/assets/img/thumbs-down.png similarity index 100% rename from frontend/text-tool/public/img/thumbs-down.png rename to frontend/text-tool/src/assets/img/thumbs-down.png diff --git a/frontend/text-tool/public/img/thumbs-up.png b/frontend/text-tool/src/assets/img/thumbs-up.png similarity index 100% rename from frontend/text-tool/public/img/thumbs-up.png rename to frontend/text-tool/src/assets/img/thumbs-up.png diff --git a/frontend/text-tool/src/components/Editor/text-item.vue b/frontend/text-tool/src/components/Editor/text-item.vue index 9b3656d7..3418e06c 100644 --- a/frontend/text-tool/src/components/Editor/text-item.vue +++ b/frontend/text-tool/src/components/Editor/text-item.vue @@ -1,62 +1,128 @@ - + diff --git a/frontend/text-tool/src/components/Editor/text-main.vue b/frontend/text-tool/src/components/Editor/text-main.vue index d419ec3b..dfc55daa 100644 --- a/frontend/text-tool/src/components/Editor/text-main.vue +++ b/frontend/text-tool/src/components/Editor/text-main.vue @@ -9,7 +9,7 @@ import { Event, ITextItem } from 'pc-editor'; import { useInjectEditor } from '../../state'; - import TextItem from './text-item.vue' + import TextItem from './text-item.vue'; const editor = useInjectEditor(); editor.addEventListener(Event.ANNOTATE_LOADED, () => { @@ -22,58 +22,15 @@ dataList.value = Array.from(dataMap.values()); console.log('==============>', dataList); } - From b2813fa15e3d3f4cbc16bd2c48363deca5ed5ade Mon Sep 17 00:00:00 2001 From: raincolor <1037428213@qq.com> Date: Thu, 27 Apr 2023 17:59:25 +0800 Subject: [PATCH 25/37] feat: text-tool style update Signed-off-by: raincolor <1037428213@qq.com> --- .../src/components/Editor/text-item.vue | 2 +- .../src/components/Editor/text-main.vue | 5 +-- .../src/components/MainView/sub/TextLong.vue | 38 +++++++++++++++++++ .../Operation/Classification/AttrValue.vue | 8 ++++ .../text-tool/src/packages/pc-editor/type.ts | 1 + 5 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 frontend/text-tool/src/components/MainView/sub/TextLong.vue diff --git a/frontend/text-tool/src/components/Editor/text-item.vue b/frontend/text-tool/src/components/Editor/text-item.vue index 3418e06c..f6daeb92 100644 --- a/frontend/text-tool/src/components/Editor/text-item.vue +++ b/frontend/text-tool/src/components/Editor/text-item.vue @@ -83,7 +83,7 @@ diff --git a/frontend/text-tool/src/components/MainView/sub/TextLong.vue b/frontend/text-tool/src/components/MainView/sub/TextLong.vue new file mode 100644 index 00000000..8048d9e7 --- /dev/null +++ b/frontend/text-tool/src/components/MainView/sub/TextLong.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/frontend/text-tool/src/components/Operation/Classification/AttrValue.vue b/frontend/text-tool/src/components/Operation/Classification/AttrValue.vue index 9efcacc9..2c41bb49 100644 --- a/frontend/text-tool/src/components/Operation/Classification/AttrValue.vue +++ b/frontend/text-tool/src/components/Operation/Classification/AttrValue.vue @@ -30,6 +30,13 @@ @change="onAttChange" v-else-if="item.type === AttrType.TEXT" /> + Date: Thu, 27 Apr 2023 18:32:49 +0800 Subject: [PATCH 26/37] fix: text-tool style update --- .../src/components/Editor/text-item.vue | 20 ++++++++++--------- .../src/components/Editor/text-main.vue | 13 +++++++++++- .../Operation/Classification/index.vue | 2 +- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/frontend/text-tool/src/components/Editor/text-item.vue b/frontend/text-tool/src/components/Editor/text-item.vue index f6daeb92..e2a6a9b2 100644 --- a/frontend/text-tool/src/components/Editor/text-item.vue +++ b/frontend/text-tool/src/components/Editor/text-item.vue @@ -1,6 +1,6 @@ @@ -83,9 +83,10 @@ diff --git a/frontend/text-tool/src/components/Editor/text-main.vue b/frontend/text-tool/src/components/Editor/text-main.vue index c0243249..534d0036 100644 --- a/frontend/text-tool/src/components/Editor/text-main.vue +++ b/frontend/text-tool/src/components/Editor/text-main.vue @@ -1,5 +1,10 @@ @@ -30,4 +35,10 @@ position: relative; min-width: 300px; } + .text-card-last { + .assistant, + .prompter { + border: 1px solid #57ccef; + } + } diff --git a/frontend/text-tool/src/components/Operation/Classification/index.vue b/frontend/text-tool/src/components/Operation/Classification/index.vue index 6981f669..4f21cdd5 100644 --- a/frontend/text-tool/src/components/Operation/Classification/index.vue +++ b/frontend/text-tool/src/components/Operation/Classification/index.vue @@ -102,7 +102,7 @@ .operation-classification { text-align: left; padding: 4px 10px; - max-height: 350px; + max-height: calc(100vh - 200px); overflow: auto; position: relative; From 86c051d8849d3862daea9264f2b29327c3d913a7 Mon Sep 17 00:00:00 2001 From: raincolor <1037428213@qq.com> Date: Thu, 27 Apr 2023 18:52:23 +0800 Subject: [PATCH 27/37] feat: upadte text-tool jump url Signed-off-by: raincolor <1037428213@qq.com> --- frontend/main/src/utils/business/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/main/src/utils/business/index.ts b/frontend/main/src/utils/business/index.ts index 86bcda01..216e0b74 100644 --- a/frontend/main/src/utils/business/index.ts +++ b/frontend/main/src/utils/business/index.ts @@ -10,7 +10,7 @@ export const goToTool = (query: any, type?: datasetTypeEnum) => { toolPath = '/tool/image'; break; case datasetTypeEnum.TEXT: - toolPath = '/#/texttool'; + toolPath = '/tool/text'; break; default: toolPath = '/tool/pc'; From b868eafab7872f6e037a53fe3ef981d93db9f063 Mon Sep 17 00:00:00 2001 From: Zhangfc846072 Date: Thu, 27 Apr 2023 18:53:29 +0800 Subject: [PATCH 28/37] fix: save data; file name; --- frontend/text-tool/src/common/Editor.ts | 49 ++++++++-------- .../src/components/Editor/text-item.vue | 22 +++++--- .../src/components/Editor/text-main.vue | 9 +-- .../text-tool/src/components/Header/index.vue | 4 +- .../src/components/Header/useHeader.ts | 20 +++---- .../packages/pc-editor/common/DataManager.ts | 56 ++++++++++++------- .../packages/pc-editor/common/DataResource.ts | 4 +- .../packages/pc-editor/common/LoadManager.ts | 1 - .../text-tool/src/packages/pc-editor/type.ts | 20 ++++--- 9 files changed, 107 insertions(+), 78 deletions(-) diff --git a/frontend/text-tool/src/common/Editor.ts b/frontend/text-tool/src/common/Editor.ts index b1121ebf..9521086f 100644 --- a/frontend/text-tool/src/common/Editor.ts +++ b/frontend/text-tool/src/common/Editor.ts @@ -1,10 +1,11 @@ -import { Editor as BaseEditor, IFrame, SourceType } from 'pc-editor'; +import { Editor as BaseEditor, IFrame, ITextItem, SourceType } from 'pc-editor'; import { IBSState } from '../type'; import { getDefault } from '../state'; import { utils, AttrType, IClassificationAttr, IUserData } from 'pc-editor'; import * as api from '../api'; import BusinessManager from './BusinessManager'; import DataManager from './DataManager'; +import * as THREE from 'three'; export default class Editor extends BaseEditor { businessManager: BusinessManager; @@ -26,7 +27,6 @@ export default class Editor extends BaseEditor { async saveObject(frames?: IFrame[], force?: boolean) { let { bsState } = this; let { classTypes } = this.state; - // let dataMeta = state.dataList[state.dataIndex]; if (bsState.saving) return; frames = frames || this.state.frames; @@ -34,29 +34,28 @@ export default class Editor extends BaseEditor { if (!force && !this.needSave(frames)) return; let dataInfos = [] as any[]; - let queryTime = frames[0].queryTime; frames.forEach((dataMeta) => { - // if (dataMeta.skipped) return; if (!dataMeta.needSave) return; - let annotates = this.dataManager.getFrameObject(dataMeta.id) || []; - if (new Date(dataMeta.queryTime).getTime() > new Date(queryTime).getTime()) - queryTime = dataMeta.queryTime; + let texts = this.dataManager.getTextItemsByFrame(dataMeta); // result object - let data = utils.convertAnnotate2Object(annotates, this); let infos = [] as any[]; let dataAnnotations = [] as any[]; - data.forEach((e) => { - let classConfig = this.getClassType(e.classId || e.classType || ''); - let objectV2 = utils.translateToObjectV2(e, classConfig); + texts.forEach((e) => { + console.log() + if (!e.needSave) return; infos.push({ - id: e.uuid || undefined, - frontId: e.frontId, - classId: classConfig?.id, - source: e.modelRun ? 'MODEL' : 'ARTIFICIAL', - sourceId: e.sourceId, - sourceType: e.sourceType, - classAttributes: objectV2, + classAttributes: { + messageId: e.id, + direction: e.direction, + type: 'CHAT_THUMB', + version: e.version, + createdBy: e.createdBy, + createdAt: e.createdAt, + + }, + id: e.backId, + frontId: e.id, }); }); @@ -83,6 +82,8 @@ export default class Editor extends BaseEditor { dataInfos: dataInfos, }; bsState.saving = true; + // console.log('================>save', objectInfo); + // return; try { // debugger await api.saveObject(objectInfo).then((keyMap) => { @@ -102,13 +103,15 @@ export default class Editor extends BaseEditor { updateBackId(keyMap: Record>) { Object.keys(keyMap).forEach((dataId) => { let dataKeyMap = keyMap[dataId]; - let annotates = this.dataManager.getFrameObject(dataId) || []; - annotates.forEach((annotate: any) => { - let frontId = annotate.uuid; + let frame = this.getFrame(dataId); + if (!frame) return; + let texts = this.dataManager.getTextItemsByFrame(frame); + texts.forEach((item: ITextItem) => { + let frontId = item.id; let backId = dataKeyMap[frontId]; + item.needSave = false; if (!backId) return; - annotate.userData.backId = backId; - // annotate.uuid = backId; + item.backId = Number(backId); }); }); } diff --git a/frontend/text-tool/src/components/Editor/text-item.vue b/frontend/text-tool/src/components/Editor/text-item.vue index e2a6a9b2..d055cfb3 100644 --- a/frontend/text-tool/src/components/Editor/text-item.vue +++ b/frontend/text-tool/src/components/Editor/text-item.vue @@ -9,23 +9,23 @@ @@ -41,23 +41,23 @@ @@ -78,7 +78,11 @@ } const props = defineProps(); - function onDirection(type: 'up' | 'down') {} + const emit = defineEmits(['changed']); + + function onDirection(item:ITextItem, type: 'up' | 'down' | '') { + emit('changed', item, type); + } From 1323146a68b5b66bbaa6c5526191615824710c1d Mon Sep 17 00:00:00 2001 From: liujian Date: Fri, 28 Apr 2023 15:38:38 +0800 Subject: [PATCH 32/37] fix: fix some bug --- .../datasets/datasetContent/components/TipModal.vue | 10 +++++++++- .../main/src/views/datasets/datasetContent/index.vue | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/frontend/main/src/views/datasets/datasetContent/components/TipModal.vue b/frontend/main/src/views/datasets/datasetContent/components/TipModal.vue index 7cc4539a..374d1316 100644 --- a/frontend/main/src/views/datasets/datasetContent/components/TipModal.vue +++ b/frontend/main/src/views/datasets/datasetContent/components/TipModal.vue @@ -24,17 +24,25 @@ import { RouteChildEnum } from '/@/enums/routeEnum'; import { useGo } from '/@/hooks/web/usePage'; import { parseParam } from '/@/utils/business/parseParams'; + import { datasetTypeEnum } from '/@/api/business/model/datasetModel'; const { query } = useRoute(); const { id } = query; const go = useGo(); const mydata = ref(); + const props = defineProps<{ + datasetType: datasetTypeEnum; + }>(); const [registerModal, { closeModal }] = useModalInner(async (data) => { mydata.value = data; }); const handleGo = () => { - go(parseParam(RouteChildEnum.DATASETS_CLASS, { id: id as unknown as string })); + if (props?.datasetType === datasetTypeEnum.TEXT) { + go(parseParam(RouteChildEnum.DATASETS_CLASSIFICATION, { id: id as unknown as string })); + } else { + go(parseParam(RouteChildEnum.DATASETS_CLASS, { id: id as unknown as string })); + } }; const handleSuccess = () => { diff --git a/frontend/main/src/views/datasets/datasetContent/index.vue b/frontend/main/src/views/datasets/datasetContent/index.vue index b15fc5fa..6762282c 100644 --- a/frontend/main/src/views/datasets/datasetContent/index.vue +++ b/frontend/main/src/views/datasets/datasetContent/index.vue @@ -19,7 +19,7 @@ - + Date: Fri, 28 Apr 2023 16:02:28 +0800 Subject: [PATCH 33/37] feat: update longText disable --- .../src/views/ontology/classes/create/CreateClassification.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/main/src/views/ontology/classes/create/CreateClassification.vue b/frontend/main/src/views/ontology/classes/create/CreateClassification.vue index cba371da..2033d5fc 100644 --- a/frontend/main/src/views/ontology/classes/create/CreateClassification.vue +++ b/frontend/main/src/views/ontology/classes/create/CreateClassification.vue @@ -216,7 +216,7 @@ watch( () => formState.inputType, (newVal) => { - if (newVal === inputTypeEnum.TEXT) { + if (newVal === inputTypeEnum.TEXT || newVal === inputTypeEnum.LONG_TEXT) { isDisabled.value = true; dataSchema.value = { options: [] }; } else { From 27d3939bb148e4d0f4cb479280d5493c6a6d74be Mon Sep 17 00:00:00 2001 From: fanyinbo <1553199396@qq.com> Date: Fri, 28 Apr 2023 18:58:43 +0800 Subject: [PATCH 34/37] Version upgraded to 0.7.0 Signed-off-by: fanyinbo <1553199396@qq.com> --- .ops/.gitlab-ci.yml | 2 +- README.md | 6 +++--- backend/Dockerfile | 2 +- backend/README.md | 2 +- backend/pom.xml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.ops/.gitlab-ci.yml b/.ops/.gitlab-ci.yml index a17eb962..269485b9 100644 --- a/.ops/.gitlab-ci.yml +++ b/.ops/.gitlab-ci.yml @@ -5,7 +5,7 @@ stages: variables: APP_NAME: "xtreme1" - APP_VERSION: "0.6.1" + APP_VERSION: "0.7.0" FRONTEND_PACKAGE_DIR: "dist" BACKEND_PACKAGE_NAME: "${APP_NAME}-backend-${APP_VERSION}-SNAPSHOT.jar" diff --git a/README.md b/README.md index 23b6620a..dc67a0d9 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@
Xtreme1 logo -![](https://img.shields.io/badge/Release-v0.6.1-green) +![](https://img.shields.io/badge/Release-v0.7.0-green) ![](https://img.shields.io/badge/License-Apache%202.0-blueviolet) [![Slack](https://img.shields.io/badge/Join-Slack-orange.svg?logo=slack)](https://join.slack.com/t/xtreme1group/shared_invite/zt-1jhk36uzr-NpdpYXeQAEHN6rYJy5_6pg) [![Twitter](https://img.shields.io/badge/Follow-Twitter-blue)](https://twitter.com/Xtreme1io) @@ -58,8 +58,8 @@ Image Data Curation (Visualizing & Debug) - [MobileNetV3](https://github.com/xi Download the latest release package and unzip it. ```bash -wget https://github.com/xtreme1-io/xtreme1/releases/download/v0.6.1/xtreme1-v0.6.1.zip -unzip -d xtreme1-v0.6.1 xtreme1-v0.6.1.zip +wget https://github.com/xtreme1-io/xtreme1/releases/download/v0.7.0/xtreme1-v0.7.0.zip +unzip -d xtreme1-v0.7.0 xtreme1-v0.7.0.zip ``` ## Start all services diff --git a/backend/Dockerfile b/backend/Dockerfile index c7d4ef94..e31f5121 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -9,7 +9,7 @@ RUN apt update && \ apt install -y iputils-ping curl wget netcat python3 python3-pip git RUN pip3 install --upgrade --force-reinstall git+https://github.com/xtreme1-io/xtreme1-sdk.git@d0cf4cc WORKDIR /app -COPY --from=build /build/target/xtreme1-backend-0.6.1-SNAPSHOT.jar ./app.jar +COPY --from=build /build/target/xtreme1-backend-0.7.0-SNAPSHOT.jar ./app.jar RUN mkdir -p config RUN wget 'https://basicai-asset.s3.us-west-2.amazonaws.com/xtreme1/xtreme1-lidar-fusion-trial.zip' -O xtreme1-lidar-fusion-trial.zip RUN wget 'https://basicai-asset.s3.us-west-2.amazonaws.com/xtreme1/xtreme1-image-trial.zip' -O xtreme1-image-trial.zip diff --git a/backend/README.md b/backend/README.md index 8e438de9..404099be 100644 --- a/backend/README.md +++ b/backend/README.md @@ -92,7 +92,7 @@ cd backend mvn package # Using local configuration to start application. -java -Dspring.profiles.active=local -jar target/xtreme1-backend-0.6.1-SNAPSHOT.jar +java -Dspring.profiles.active=local -jar target/xtreme1-backend-0.7.0-SNAPSHOT.jar ``` Now you can access the backend service at `http://localhost:8080/`. diff --git a/backend/pom.xml b/backend/pom.xml index 39c0801f..4a415144 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -13,7 +13,7 @@ ai.basic xtreme1-backend - 0.6.1-SNAPSHOT + 0.7.0-SNAPSHOT Xtreme1 Backend From b2035fb32282bd666476a528bc713cc2ed761b58 Mon Sep 17 00:00:00 2001 From: raincolor <1037428213@qq.com> Date: Fri, 28 Apr 2023 19:03:25 +0800 Subject: [PATCH 35/37] fix: onToggleValid bugfix --- frontend/text-tool/src/components/Header/useHeader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/text-tool/src/components/Header/useHeader.ts b/frontend/text-tool/src/components/Header/useHeader.ts index eea96816..102593e1 100644 --- a/frontend/text-tool/src/components/Header/useHeader.ts +++ b/frontend/text-tool/src/components/Header/useHeader.ts @@ -181,7 +181,7 @@ export default function useHeader() { "you don't have any annotation yet, are you sure you want to submit this data? If you can't annotate this data, you'd better mark this data as invalid. Cancel/ submit anyway", }) .then(async () => { - await onToggleValid(); + // await onToggleValid(); }) .catch(() => { continueFlag = false; From c8d41f83156e996583dd764878b18e4155271386 Mon Sep 17 00:00:00 2001 From: raincolor <1037428213@qq.com> Date: Fri, 28 Apr 2023 19:07:30 +0800 Subject: [PATCH 36/37] pc-tool onToggleValid bugfix --- frontend/pc-tool/src/components/Header/useHeader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/pc-tool/src/components/Header/useHeader.ts b/frontend/pc-tool/src/components/Header/useHeader.ts index b59a5b4d..43c8449f 100644 --- a/frontend/pc-tool/src/components/Header/useHeader.ts +++ b/frontend/pc-tool/src/components/Header/useHeader.ts @@ -181,7 +181,7 @@ export default function useHeader() { "you don't have any annotation yet, are you sure you want to submit this data? If you can't annotate this data, you'd better mark this data as invalid. Cancel/ submit anyway", }) .then(async () => { - await onToggleValid(); + // await onToggleValid(); }) .catch(() => { continueFlag = false; From 4150e6c1951f599b96fd9b383f56eb2ae5aaeb00 Mon Sep 17 00:00:00 2001 From: fanyinbo <1553199396@qq.com> Date: Fri, 28 Apr 2023 19:38:39 +0800 Subject: [PATCH 37/37] Version upgraded to 0.7.0 Signed-off-by: fanyinbo <1553199396@qq.com> --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index b3c8b985..fedb7f0a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -65,7 +65,7 @@ services: retries: 10 backend: # By default, Compose will pull image from Docker Hub when no local image found. - image: basicai/xtreme1-backend:v0.6.1 + image: basicai/xtreme1-backend:v0.7.0 # Uncomment this line and comment previous line to build image locally, not pull from Docker Hub. # build: ./backend ports: @@ -95,7 +95,7 @@ services: condition: service_healthy frontend: # By default, Compose will pull image from Docker Hub when no local image found. - image: basicai/xtreme1-frontend:v0.6.1 + image: basicai/xtreme1-frontend:v0.7.0 # Uncomment this line and comment previous line to build image locally, not pull from Docker Hub. # build: ./frontend ports: