From 1db08e05be18f4c138236d8499310487d192cd8d Mon Sep 17 00:00:00 2001 From: Kirk Swenson Date: Wed, 18 Dec 2024 12:23:25 -0800 Subject: [PATCH] feat: import/export case card from/to v2 documents (#1691) --- .../components/case-card/case-card-model.ts | 3 +- .../case-card/case-card-registration.test.ts | 75 ++ .../case-card/case-card-registration.ts | 103 ++- .../case-table/case-table-registration.ts | 6 +- .../data-interactive-type-utils.ts | 5 +- v3/src/test/v2/mammals-case-card.codap | 860 ++++++++++++++++++ v3/src/v2/codap-v2-document.ts | 10 + v3/src/v2/codap-v2-tile-importers.ts | 5 +- v3/src/v2/codap-v2-types.ts | 14 +- v3/src/v2/import-v2-document.ts | 7 +- 10 files changed, 1038 insertions(+), 50 deletions(-) create mode 100644 v3/src/components/case-card/case-card-registration.test.ts create mode 100644 v3/src/test/v2/mammals-case-card.codap diff --git a/v3/src/components/case-card/case-card-model.ts b/v3/src/components/case-card/case-card-model.ts index 78a260a58d..7dffff2c63 100644 --- a/v3/src/components/case-card/case-card-model.ts +++ b/v3/src/components/case-card/case-card-model.ts @@ -12,7 +12,7 @@ export const CaseCardModel = TileContentModel .named("CaseCardModel") .props({ type: types.optional(types.literal(kCaseCardTileType), kCaseCardTileType), - // key is collection id; value is width + // key is collection id; value is percentage width attributeColumnWidths: types.map(types.number), }) .views(self => ({ @@ -22,6 +22,7 @@ export const CaseCardModel = TileContentModel get metadata() { return getTileCaseMetadata(self) }, + // value is a percentage width attributeColumnWidth(collectionId: string) { return self.attributeColumnWidths.get(collectionId) } diff --git a/v3/src/components/case-card/case-card-registration.test.ts b/v3/src/components/case-card/case-card-registration.test.ts new file mode 100644 index 0000000000..f084a2c4cb --- /dev/null +++ b/v3/src/components/case-card/case-card-registration.test.ts @@ -0,0 +1,75 @@ +import { createCodapDocument } from "../../models/codap/create-codap-document" +import { FreeTileRow, IFreeTileRow } from "../../models/document/free-tile-row" +import { getTileComponentInfo } from "../../models/tiles/tile-component-info" +import { getTileContentInfo } from "../../models/tiles/tile-content-info" +import { getSharedModelManager } from "../../models/tiles/tile-environment" +import { ITileModelSnapshotIn } from "../../models/tiles/tile-model" +import { safeJsonParse } from "../../utilities/js-utils" +import { CodapV2Document } from "../../v2/codap-v2-document" +import { exportV2Component } from "../../v2/codap-v2-tile-exporters" +import { importV2Component } from "../../v2/codap-v2-tile-importers" +import { ICodapV2CaseCardStorage, ICodapV2DocumentJson } from "../../v2/codap-v2-types" +import { kCaseCardTileType } from "./case-card-defs" +import { isCaseCardModel } from "./case-card-model" +import "./case-card-registration" + +const fs = require("fs") +const path = require("path") + +describe("Case card registration", () => { + it("registers content and component info", () => { + const cardContentInfo = getTileContentInfo(kCaseCardTileType) + expect(cardContentInfo).toBeDefined() + expect(getTileComponentInfo(kCaseCardTileType)).toBeDefined() + const cardContent = cardContentInfo?.defaultContent() + expect(cardContent).toBeDefined() + }) + + it("imports/exports v2 case card components", () => { + const file = path.join(__dirname, "../../test/v2", "mammals-case-card.codap") + const mammalsJson = fs.readFileSync(file, "utf8") + const mammalsDoc = safeJsonParse(mammalsJson)! + const v2Document = new CodapV2Document(mammalsDoc) + + const codapDoc = createCodapDocument() + const docContent = codapDoc.content! + docContent.setRowCreator(() => FreeTileRow.create()) + const sharedModelManager = getSharedModelManager(docContent) + const mockInsertTile = jest.fn((tileSnap: ITileModelSnapshotIn) => { + return docContent?.insertTileSnapshotInDefaultRow(tileSnap) + }) + + const [v2TableComponent, v2GuideComponent, v2CardComponent] = v2Document.components + expect(v2TableComponent.type).toBe("DG.TableView") + expect(v2GuideComponent.type).toBe("DG.GuideView") + expect(v2CardComponent.type).toBe("DG.CaseCard") + + const tile = importV2Component({ + v2Component: v2CardComponent, + v2Document, + sharedModelManager, + insertTile: mockInsertTile + })! + expect(tile).toBeDefined() + expect(mockInsertTile).toHaveBeenCalledTimes(1) + const cardContent = isCaseCardModel(tile?.content) ? tile?.content : undefined + expect(getTileContentInfo(kCaseCardTileType)!.getTitle(tile)).toBe("Mammals") + expect(cardContent?.data).toBeDefined() + expect(cardContent?.attributeColumnWidths.size).toBe(1) + + const tileWithInvalidComponent = importV2Component({ + v2Component: {} as any, + v2Document, + insertTile: mockInsertTile + }) + expect(tileWithInvalidComponent).toBeUndefined() + + const row = docContent.getRowByIndex(0) as IFreeTileRow + const cardExport = exportV2Component({ tile, row, sharedModelManager }) + expect(cardExport?.type).toBe("DG.CaseCard") + const cardStorage = cardExport!.componentStorage as ICodapV2CaseCardStorage + expect(cardStorage._links_?.context).toBeDefined() + expect(Object.keys(cardStorage.columnWidthMap!).length).toBe(1) + }) + +}) diff --git a/v3/src/components/case-card/case-card-registration.ts b/v3/src/components/case-card/case-card-registration.ts index c970d56efd..3dbf3e8fc5 100644 --- a/v3/src/components/case-card/case-card-registration.ts +++ b/v3/src/components/case-card/case-card-registration.ts @@ -1,17 +1,28 @@ -import { caseTableCardComponentHandler } from "../case-tile-common/case-tile-component-handler" +import { SetOptional, SetRequired } from "type-fest" +import CardIcon from "../../assets/icons/icon-case-card.svg" import { registerComponentHandler } from "../../data-interactive/handlers/component-handler" +import { IFreeTileInRowOptions } from "../../models/document/free-tile-row" +import { getTileDataSet } from "../../models/shared/shared-data-utils" import { registerTileComponentInfo } from "../../models/tiles/tile-component-info" import { registerTileContentInfo } from "../../models/tiles/tile-content-info" -import { CaseCardComponent } from "./case-card-component" -import { kCaseCardTileType, kV2CaseCardType } from "./case-card-defs" -import { CaseCardModel } from "./case-card-model" -import { CaseTileTitleBar } from "../case-tile-common/case-tile-title-bar" -import CardIcon from '../../assets/icons/icon-case-card.svg' +import { ITileModelSnapshotIn } from "../../models/tiles/tile-model" +import { toV2Id, toV3CollectionId, toV3Id } from "../../utilities/codap-utils" import { t } from "../../utilities/translation/translate" -import { getTileDataSet } from "../../models/shared/shared-data-utils" +import { registerV2TileExporter } from "../../v2/codap-v2-tile-exporters" +import { LayoutTransformFn, registerV2TileImporter } from "../../v2/codap-v2-tile-importers" +import { + guidLink, ICodapV2BaseComponentStorage, ICodapV2CaseCardStorage, isV2CaseCardComponent +} from "../../v2/codap-v2-types" +import { caseTableCardComponentHandler } from "../case-tile-common/case-tile-component-handler" +import { CaseTileTitleBar } from "../case-tile-common/case-tile-title-bar" import { CaseTileInspector } from "../case-tile-common/inspector-panel/case-tile-inspector" +import { CaseCardComponent } from "./case-card-component" +import { kCaseCardTileType, kV2CaseCardType } from "./case-card-defs" +import { CaseCardModel, ICaseCardSnapshot, isCaseCardModel } from "./case-card-model" export const kCaseCardIdPrefix = "CARD" +export const kCaseCardDefaultHeight = 400 +export const kCaseCardDefaultWidth = 350 registerTileContentInfo({ type: kCaseCardTileType, @@ -32,41 +43,76 @@ registerTileComponentInfo({ InspectorPanel: CaseTileInspector, tileEltClass: "codap-case-card", Icon: CardIcon, - defaultWidth: 350, - defaultHeight: 400 + defaultWidth: kCaseCardDefaultWidth, + defaultHeight: kCaseCardDefaultHeight }) registerComponentHandler(kV2CaseCardType, caseTableCardComponentHandler) -/* -registerV2TileImporter("DG.CardView", ({ v2Component, v2Document, sharedModelManager, insertTile }) => { - if (!isV2CardComponent(v2Component)) return +registerV2TileExporter(kCaseCardTileType, ({ tile }) => { + const cardContent = isCaseCardModel(tile.content) ? tile.content : undefined + let componentStorage: Maybe> + const dataSet = cardContent?.data + const columnWidthMap: Record = {} + cardContent?.attributeColumnWidths.forEach((widthPct, collectionId) => { + columnWidthMap[String(toV2Id(String(collectionId)))] = widthPct + }) + if (dataSet) { + componentStorage = { + _links_: { context: guidLink("DG.DataContextRecord", toV2Id(dataSet.id)) }, + columnWidthMap, + title: tile._title + } + } + return { type: "DG.CaseCard", componentStorage } +}) + +registerV2TileImporter("DG.CaseCard", ({ v2Component, v2Document, sharedModelManager, insertTile }) => { + if (!isV2CaseCardComponent(v2Component)) return - const { title = "", _links_, attributeWidths } = v2Component.componentStorage + const { + guid, + componentStorage: { name, title = "", _links_, isActive, columnWidthPct, columnWidthMap } + } = v2Component - const content: SetRequired = { + const content: SetRequired = { type: kCaseCardTileType, - columnWidths: {} + attributeColumnWidths: {} } const contextId = _links_.context.id const { data, metadata } = v2Document.getDataAndMetadata(contextId) - // stash the card's column widths in the content - attributeWidths?.forEach(entry => { - const v2Attr = v2Document.getV2Attribute(entry._links_.attr.id) - if (isCodapV2Attribute(v2Attr)) { - const attrId = data?.dataSet.attrIDFromName(v2Attr.name) - if (attrId && entry.width) { - content.columnWidths[attrId] = entry.width - } + // some documents (presumably preceding hierarchy support) have a single percentage width + if (columnWidthPct != null) { + const collection = v2Document.getV2CollectionByIndex() + if (collection) { + content.attributeColumnWidths[toV3CollectionId(collection.guid)] = Number(columnWidthPct) } - }) + } + // most documents have a map of collection id to percentage width + else if (columnWidthMap) { + Object.keys(columnWidthMap).forEach(collectionId => { + const columnWidth = columnWidthMap[collectionId] + if (columnWidth) { + content.attributeColumnWidths[toV3CollectionId(collectionId)] = columnWidth + } + }) + } - const cardTileSnap: ITileModelSnapshotIn = { id: typedId(kCaseCardIdPrefix), title, content } - const cardTile = insertTile(cardTileSnap) + const cardTileSnap: ITileModelSnapshotIn = { + id: toV3Id(kCaseCardIdPrefix, guid), name, _title: title, content + } + const transform: LayoutTransformFn = (options: IFreeTileInRowOptions) => { + const { width, ...others } = options + // v3 case card is wider than v2 + return width != null && width < kCaseCardDefaultWidth ? { width: kCaseCardDefaultWidth, ...others } : options + } + const cardTile = insertTile(cardTileSnap, transform) - // Make sure metadata knows this is the table tile and it is the last shown - metadata?.setLastShownTableOrCardTileId(cardTile?.id) + // Make sure metadata knows this is the case card tile and it is the last shown + if (isActive) { + metadata?.setLastShownTableOrCardTileId(cardTile?.id) + } metadata?.setCaseCardTileId(cardTile?.id) // add links to shared models @@ -77,4 +123,3 @@ registerV2TileImporter("DG.CardView", ({ v2Component, v2Document, sharedModelMan return cardTile }) -*/ diff --git a/v3/src/components/case-table/case-table-registration.ts b/v3/src/components/case-table/case-table-registration.ts index 1d33051e0c..5b0c63e0e7 100644 --- a/v3/src/components/case-table/case-table-registration.ts +++ b/v3/src/components/case-table/case-table-registration.ts @@ -74,7 +74,7 @@ registerV2TileExporter(kCaseTableTileType, ({ tile }) => { registerV2TileImporter("DG.TableView", ({ v2Component, v2Document, sharedModelManager, insertTile }) => { if (!isV2TableComponent(v2Component)) return - const { guid, componentStorage: { name, title = "", _links_, attributeWidths } } = v2Component + const { guid, componentStorage: { name, title = "", _links_, isActive, attributeWidths } } = v2Component // Handle broken tables that don't have any links if (!_links_) return @@ -103,7 +103,9 @@ registerV2TileImporter("DG.TableView", ({ v2Component, v2Document, sharedModelMa const tableTile = insertTile(tableTileSnap) // Make sure metadata knows this is the table tile and it is the last shown - metadata?.setLastShownTableOrCardTileId(tableTile?.id) + if (isActive) { + metadata?.setLastShownTableOrCardTileId(tableTile?.id) + } metadata?.setCaseTableTileId(tableTile?.id) // add links to shared models diff --git a/v3/src/data-interactive/data-interactive-type-utils.ts b/v3/src/data-interactive/data-interactive-type-utils.ts index 0cf2431e8e..7bafe74e48 100644 --- a/v3/src/data-interactive/data-interactive-type-utils.ts +++ b/v3/src/data-interactive/data-interactive-type-utils.ts @@ -203,8 +203,9 @@ export function convertDataSetToV2(dataSet: IDataSet, exportCases = false): ICod // metadata, // preventReorg, // TODO_V2_EXPORT - setAsideItems: [] - // contextStorage + setAsideItems: [], + // TODO_V2_IMPORT, TODO_V2_EXPORT + contextStorage: { _links_: { selectedCases: [] } } } } diff --git a/v3/src/test/v2/mammals-case-card.codap b/v3/src/test/v2/mammals-case-card.codap new file mode 100644 index 0000000000..2c545d2790 --- /dev/null +++ b/v3/src/test/v2/mammals-case-card.codap @@ -0,0 +1,860 @@ +{ + "name": "Mammals", + "guid": 1, + "id": 1, + "components": [ + { + "type": "DG.TableView", + "guid": 40, + "id": 40, + "componentStorage": { + "isActive": false, + "_links_": { + "context": { + "type": "DG.DataContextRecord", + "id": 2 + } + }, + "attributeWidths": [ + { + "_links_": { + "attr": { + "type": "DG.Attribute", + "id": 12 + } + }, + "width": 45 + } + ], + "rowHeights": [], + "title": "Mammals", + "name": "Mammals", + "userSetTitle": false, + "cannotClose": false + }, + "layout": { + "width": 583, + "height": 237, + "left": 5, + "top": 5, + "isVisible": false, + "zIndex": 101, + "right": 588, + "bottom": 242 + }, + "savedHeight": null + }, + { + "type": "DG.GuideView", + "guid": 41, + "id": 41, + "componentStorage": { + "title": "Mammals Sample Guide", + "items": [ + { + "itemTitle": "Get Started", + "url": "../../../../extn/example-documents/guides/Mammals/mammals_getstarted.html" + } + ], + "currentItemIndex": 0, + "isVisible": false, + "name": "Mammals Sample Guide", + "userSetTitle": false, + "cannotClose": false + }, + "layout": { + "left": 665, + "top": 10, + "width": 416, + "height": 510, + "isVisible": false, + "zIndex": 15, + "right": 1081, + "bottom": 520 + }, + "savedHeight": null + }, + { + "type": "DG.CaseCard", + "guid": 42, + "id": 42, + "componentStorage": { + "isActive": true, + "_links_": { + "context": { + "type": "DG.DataContextRecord", + "id": 2 + } + }, + "columnWidthMap": { + "Mammals": 0.4362 + }, + "title": "Mammals", + "userSetTitle": false, + "cannotClose": false + }, + "layout": { + "top": 5, + "left": 5, + "width": 200, + "height": 399, + "zIndex": 103, + "isVisible": true, + "right": 205, + "bottom": 404 + }, + "savedHeight": null + } + ], + "contexts": [ + { + "type": "DG.DataContext", + "document": 1, + "guid": 2, + "id": 2, + "flexibleGroupingChangeFlag": false, + "name": "Mammals", + "title": "Mammals", + "collections": [ + { + "areParentChildLinksConfigured": false, + "attrs": [ + { + "name": "Mammal", + "type": "none", + "title": "Mammal", + "cid": "id:InjuyZ40TyUIE0G0", + "description": "The species of the mammal", + "_categoryMap": { + "__order": [ + "African Elephant", + "Asian Elephant", + "Big Brown Bat", + "Bottlenose Dolphin", + "Cheetah", + "Chimpanzee", + "Domestic Cat", + "Donkey", + "Giraffe", + "Gray Wolf", + "Grey Seal", + "Ground Squirrel", + "Horse", + "House Mouse", + "Human", + "Jaguar", + "Killer Whale", + "Lion", + "N. American Opossum", + "Nine-Banded Armadillo", + "Owl Monkey", + "Patas Monkey", + "Pig", + "Pronghorn Antelope", + "Rabbit", + "Red Fox", + "Spotted Hyena" + ], + "African Elephant": "#FFB300", + "Asian Elephant": "#803E75", + "Big Brown Bat": "#FF6800", + "Bottlenose Dolphin": "#A6BDD7", + "Cheetah": "#C10020", + "Chimpanzee": "#CEA262", + "Domestic Cat": "#817066", + "Donkey": "#007D34", + "Giraffe": "#00538A", + "Gray Wolf": "#F13A13", + "Grey Seal": "#53377A", + "Ground Squirrel": "#FF8E00", + "Horse": "#B32851", + "House Mouse": "#F4C800", + "Human": "#7F180D", + "Jaguar": "#93AA00", + "Killer Whale": "#593315", + "Lion": "#232C16", + "N. American Opossum": "#FF7A5C", + "Nine-Banded Armadillo": "#F6768E", + "Owl Monkey": "#FFB300", + "Patas Monkey": "#803E75", + "Pig": "#FF6800", + "Pronghorn Antelope": "#A6BDD7", + "Rabbit": "#C10020", + "Red Fox": "#CEA262", + "Spotted Hyena": "#817066" + }, + "blockDisplayOfEmptyCategories": true, + "editable": true, + "hidden": false, + "renameable": true, + "deleteable": true, + "guid": 4, + "id": 4, + "precision": 2, + "unit": null + }, + { + "name": "Order", + "type": "none", + "title": "Order", + "cid": "id:o7h0AbAW6yg0Qxtc", + "description": "The order of the mammal", + "blockDisplayOfEmptyCategories": true, + "editable": true, + "hidden": false, + "renameable": true, + "deleteable": true, + "guid": 5, + "id": 5, + "precision": 2, + "unit": null + }, + { + "name": "LifeSpan", + "type": "numeric", + "title": "LifeSpan", + "cid": "id:XncCNuJYR334Wtga", + "description": "", + "_categoryMap": { + "3": "#00538A", + "5": "#F4C800", + "7": "#593315", + "9": "#007D34", + "10": "#7F180D", + "12": "#93AA00", + "14": "#A6BDD7", + "15": "#B32851", + "16": "#CEA262", + "19": "#803E75", + "20": "#53377A", + "25": "#FF6800", + "30": "#817066", + "40": "#C10020", + "50": "#FF8E00", + "70": "#FFB300", + "80": "#F13A13", + "__order": [ + "10", + "12", + "14", + "15", + "16", + "19", + "20", + "25", + "3", + "30", + "40", + "5", + "50", + "7", + "70", + "80", + "9" + ] + }, + "blockDisplayOfEmptyCategories": true, + "editable": true, + "hidden": false, + "renameable": true, + "deleteable": true, + "guid": 6, + "id": 6, + "precision": 2, + "unit": "years" + }, + { + "name": "Height", + "type": "none", + "title": "Height", + "cid": "id:ErF9H4uyv9ijXOSH", + "description": "The average height of the mammal.", + "blockDisplayOfEmptyCategories": true, + "editable": true, + "hidden": false, + "renameable": true, + "deleteable": true, + "guid": 7, + "id": 7, + "precision": 2, + "unit": "meters" + }, + { + "name": "Mass", + "type": "numeric", + "title": "Mass", + "cid": "id:o-8H-QTgtzSOXn3V", + "description": "The mass of the mammal", + "blockDisplayOfEmptyCategories": true, + "editable": true, + "hidden": false, + "renameable": true, + "deleteable": true, + "guid": 8, + "id": 8, + "precision": 2, + "unit": "kg" + }, + { + "name": "Sleep", + "type": "numeric", + "title": "Sleep", + "cid": "id:gap1De-ZRbMfMBN-", + "description": "The number of hours the animal sleeps in a 24 hour period", + "blockDisplayOfEmptyCategories": true, + "editable": true, + "hidden": false, + "renameable": true, + "deleteable": true, + "guid": 9, + "id": 9, + "precision": 2, + "unit": "hours" + }, + { + "name": "Speed", + "type": "none", + "title": "Speed", + "cid": "id:MzIzA_UXuOQVKMuA", + "description": "The average speed of the mammal.", + "blockDisplayOfEmptyCategories": true, + "editable": true, + "hidden": false, + "renameable": true, + "deleteable": true, + "guid": 10, + "id": 10, + "precision": 2, + "unit": "km/h" + }, + { + "name": "Habitat", + "type": null, + "title": "Habitat", + "cid": "id:Kp31V435su8kub05", + "description": "", + "_categoryMap": { + "land": { + "h": 0.4444444444444444, + "s": 0.6666666666666666, + "b": 1, + "colorString": "rgb(85,255,198)" + }, + "water": { + "h": 0.711111111111111, + "s": 0.6666666666666666, + "b": 1, + "colorString": "rgb(130,85,255)" + }, + "both": { + "h": 0.9777777777777777, + "s": 0.6666666666666666, + "b": 1, + "colorString": "rgb(255,85,108)" + }, + "__order": [ + "land", + "water", + "both" + ] + }, + "blockDisplayOfEmptyCategories": true, + "editable": true, + "hidden": false, + "renameable": true, + "deleteable": true, + "guid": 11, + "id": 11, + "precision": 2, + "unit": null + }, + { + "name": "Diet", + "type": null, + "title": "Diet", + "cid": "id:yL3DuAAfslqGglkY", + "description": "", + "_categoryMap": { + "__order": [ + "both", + "meat", + "plants" + ], + "plants": "#FFB300", + "meat": "#803E75", + "both": "#FF6800" + }, + "blockDisplayOfEmptyCategories": true, + "editable": true, + "hidden": false, + "renameable": true, + "deleteable": true, + "guid": 12, + "id": 12, + "precision": 2, + "unit": null + } + ], + "cases": [ + { + "guid": 13, + "id": 13, + "itemID": "id:N952Um92Mv8VpYod", + "values": { + "Mammal": "African Elephant", + "Order": "Proboscidae", + "LifeSpan": 70, + "Height": 4, + "Mass": 6400, + "Sleep": 3, + "Speed": 40, + "Habitat": "land", + "Diet": "plants" + } + }, + { + "guid": 14, + "id": 14, + "itemID": "id:wX1bJv_Rr6BaH38F", + "values": { + "Mammal": "Asian Elephant", + "Order": "Proboscidae", + "LifeSpan": 70, + "Height": 3, + "Mass": 5000, + "Sleep": 4, + "Speed": 40, + "Habitat": "land", + "Diet": "plants" + } + }, + { + "guid": 15, + "id": 15, + "itemID": "id:YOgifJd175jT8FCx", + "values": { + "Mammal": "Big Brown Bat", + "Order": "Chiroptera", + "LifeSpan": 19, + "Height": 0.1, + "Mass": 0.02, + "Sleep": 20, + "Speed": 40, + "Habitat": "land", + "Diet": "meat" + } + }, + { + "guid": 16, + "id": 16, + "itemID": "id:Nf48qRyCy4xjqcz9", + "values": { + "Mammal": "Bottlenose Dolphin", + "Order": "Cetacea", + "LifeSpan": 25, + "Height": 3.5, + "Mass": 635, + "Sleep": 5, + "Speed": 37, + "Habitat": "water", + "Diet": "meat" + } + }, + { + "guid": 17, + "id": 17, + "itemID": "id:9odeu0fj6ky8wdH_", + "values": { + "Mammal": "Cheetah", + "Order": "Carnivora", + "LifeSpan": 14, + "Height": 1.5, + "Mass": 50, + "Sleep": 12, + "Speed": 110, + "Habitat": "land", + "Diet": "meat" + } + }, + { + "guid": 18, + "id": 18, + "itemID": "id:RySTXYOXXlXvvm_q", + "values": { + "Mammal": "Chimpanzee", + "Order": "Primate", + "LifeSpan": 40, + "Height": 1.5, + "Mass": 68, + "Sleep": 10, + "Speed": "", + "Habitat": "land", + "Diet": "both" + } + }, + { + "guid": 19, + "id": 19, + "itemID": "id:WFlwFTmg8mwPymLr", + "values": { + "Mammal": "Domestic Cat", + "Order": "Carnivora", + "LifeSpan": 16, + "Height": 0.8, + "Mass": 4.5, + "Sleep": 12, + "Speed": 50, + "Habitat": "land", + "Diet": "meat" + } + }, + { + "guid": 20, + "id": 20, + "itemID": "id:xIK9txv8VvuhCKcI", + "values": { + "Mammal": "Donkey", + "Order": "Perissodactyla", + "LifeSpan": 40, + "Height": 1.2, + "Mass": 187, + "Sleep": 3, + "Speed": 50, + "Habitat": "land", + "Diet": "plants" + } + }, + { + "guid": 21, + "id": 21, + "itemID": "id:28RNSIFYh8AiNrl5", + "values": { + "Mammal": "Giraffe", + "Order": "Artiodactyla", + "LifeSpan": 25, + "Height": 5, + "Mass": 1100, + "Sleep": 2, + "Speed": 50, + "Habitat": "land", + "Diet": "plants" + } + }, + { + "guid": 22, + "id": 22, + "itemID": "id:WLjBlmS7GdM9uSse", + "values": { + "Mammal": "Gray Wolf", + "Order": "Carnivora", + "LifeSpan": 16, + "Height": 1.6, + "Mass": 80, + "Sleep": 13, + "Speed": 64, + "Habitat": "land", + "Diet": "meat" + } + }, + { + "guid": 23, + "id": 23, + "itemID": "id:CGr1srwnLEEIdP1D", + "values": { + "Mammal": "Grey Seal", + "Order": "Pinnipedia", + "LifeSpan": 30, + "Height": 2.1, + "Mass": 275, + "Sleep": 6, + "Speed": 19, + "Habitat": "both", + "Diet": "meat" + } + }, + { + "guid": 24, + "id": 24, + "itemID": "id:4Lwfo98rsElRT0RZ", + "values": { + "Mammal": "Ground Squirrel", + "Order": "Rodentia", + "LifeSpan": 9, + "Height": 0.3, + "Mass": 0.1, + "Sleep": 15, + "Speed": 19, + "Habitat": "land", + "Diet": "both" + } + }, + { + "guid": 25, + "id": 25, + "itemID": "id:7jF33hpK2vyZmx2U", + "values": { + "Mammal": "Horse", + "Order": "Perissodactyla", + "LifeSpan": 25, + "Height": 1.5, + "Mass": 521, + "Sleep": 3, + "Speed": 69, + "Habitat": "land", + "Diet": "plants" + } + }, + { + "guid": 26, + "id": 26, + "itemID": "id:xgb_uTSL4Qvv6zNg", + "values": { + "Mammal": "House Mouse", + "Order": "Rodentia", + "LifeSpan": 3, + "Height": 0.1, + "Mass": 0.03, + "Sleep": 12, + "Speed": 13, + "Habitat": "land", + "Diet": "both" + } + }, + { + "guid": 27, + "id": 27, + "itemID": "id:GcvKr2ga0qmXFKli", + "values": { + "Mammal": "Human", + "Order": "Primate", + "LifeSpan": 80, + "Height": 1.9, + "Mass": 80, + "Sleep": 8, + "Speed": 45, + "Habitat": "land", + "Diet": "both" + } + }, + { + "guid": 28, + "id": 28, + "itemID": "id:RM5egngZRUngXQ_q", + "values": { + "Mammal": "Jaguar", + "Order": "Carnivora", + "LifeSpan": 20, + "Height": 1.8, + "Mass": 115, + "Sleep": 11, + "Speed": 60, + "Habitat": "land", + "Diet": "meat" + } + }, + { + "guid": 29, + "id": 29, + "itemID": "id:5zV6bHbB9fziJgfh", + "values": { + "Mammal": "Killer Whale", + "Order": "Cetacea", + "LifeSpan": 50, + "Height": 6.5, + "Mass": 4000, + "Sleep": "", + "Speed": 48, + "Habitat": "water", + "Diet": "meat" + } + }, + { + "guid": 30, + "id": 30, + "itemID": "id:pvQRbmlrsc4KjadN", + "values": { + "Mammal": "Lion", + "Order": "Carnivora", + "LifeSpan": 15, + "Height": 2.5, + "Mass": 250, + "Sleep": 20, + "Speed": 80, + "Habitat": "land", + "Diet": "meat" + } + }, + { + "guid": 31, + "id": 31, + "itemID": "id:OKQMSn2qXxh2iXU0", + "values": { + "Mammal": "N. American Opossum", + "Order": "Didelphimorphia", + "LifeSpan": 5, + "Height": 0.5, + "Mass": 5, + "Sleep": 19, + "Speed": "", + "Habitat": "land", + "Diet": "both" + } + }, + { + "guid": 32, + "id": 32, + "itemID": "id:FCBgwD8pWqg2XiYF", + "values": { + "Mammal": "Nine-Banded Armadillo", + "Order": "Xenarthra", + "LifeSpan": 10, + "Height": 0.6, + "Mass": 7, + "Sleep": 17, + "Speed": 1, + "Habitat": "land", + "Diet": "meat" + } + }, + { + "guid": 33, + "id": 33, + "itemID": "id:H601bUrD97ntvNTe", + "values": { + "Mammal": "Owl Monkey", + "Order": "Primate", + "LifeSpan": 12, + "Height": 0.4, + "Mass": 1, + "Sleep": 17, + "Speed": "", + "Habitat": "land", + "Diet": "both" + } + }, + { + "guid": 34, + "id": 34, + "itemID": "id:g0IHVcufgec6bY-t", + "values": { + "Mammal": "Patas Monkey", + "Order": "Primate", + "LifeSpan": 20, + "Height": 0.9, + "Mass": 13, + "Sleep": "", + "Speed": 55, + "Habitat": "land", + "Diet": "both" + } + }, + { + "guid": 35, + "id": 35, + "itemID": "id:jwA0mzwWTtSor4z2", + "values": { + "Mammal": "Pig", + "Order": "Artiodactyla", + "LifeSpan": 10, + "Height": 1, + "Mass": 192, + "Sleep": 8, + "Speed": 18, + "Habitat": "land", + "Diet": "both" + } + }, + { + "guid": 36, + "id": 36, + "itemID": "id:Vkwo_GDnvokCG370", + "values": { + "Mammal": "Pronghorn Antelope", + "Order": "Artiodactyla", + "LifeSpan": 10, + "Height": 0.9, + "Mass": 70, + "Sleep": "", + "Speed": 98, + "Habitat": "land", + "Diet": "plants" + } + }, + { + "guid": 37, + "id": 37, + "itemID": "id:nLalWFGGQa5DKNWu", + "values": { + "Mammal": "Rabbit", + "Order": "Lagomorpha", + "LifeSpan": 5, + "Height": 0.5, + "Mass": 3, + "Sleep": 11, + "Speed": 56, + "Habitat": "land", + "Diet": "plants" + } + }, + { + "guid": 38, + "id": 38, + "itemID": "id:IsU9bAg4zYxNh28y", + "values": { + "Mammal": "Red Fox", + "Order": "Carnivora", + "LifeSpan": 7, + "Height": 0.8, + "Mass": 5, + "Sleep": 10, + "Speed": 48, + "Habitat": "land", + "Diet": "both" + } + }, + { + "guid": 39, + "id": 39, + "itemID": "id:ETeoc5obJzOa_shY", + "values": { + "Mammal": "Spotted Hyena", + "Order": "Carnivora", + "LifeSpan": 25, + "Height": 0.9, + "Mass": 70, + "Sleep": 18, + "Speed": 64, + "Habitat": "land", + "Diet": "meat" + } + } + ], + "caseName": null, + "childAttrName": null, + "collapseChildren": null, + "guid": 3, + "id": 3, + "name": "Mammals", + "title": "Mammals", + "type": "DG.Collection" + } + ], + "description": "", + "metadata": null, + "preventReorg": false, + "setAsideItems": [], + "contextStorage": { + "_links_": { + "selectedCases": [] + } + } + } + ], + "globalValues": [], + "appName": "DG", + "appVersion": "2.0", + "appBuildNum": "0730", + "lang": "en", + "idCount": 42, + "metadata": {} +} diff --git a/v3/src/v2/codap-v2-document.ts b/v3/src/v2/codap-v2-document.ts index 8411597ce9..bbf9eed230 100644 --- a/v3/src/v2/codap-v2-document.ts +++ b/v3/src/v2/codap-v2-document.ts @@ -88,6 +88,16 @@ export class CodapV2Document { return parentCaseId != null ? this.guidMap.get(parentCaseId)?.object as ICodapV2Case | undefined : undefined } + getV2Collection(v2Id: number) { + const entry = this.guidMap.get(v2Id) + return entry?.type === "DG.Collection" ? entry.object as ICodapV2Collection : undefined + } + + getV2CollectionByIndex(collectionIndex = 0, contextIndex = 0) { + const context = this.contexts[contextIndex] + return isV2InternalContext(context) ? context.collections[collectionIndex] : undefined + } + getV2Attribute(v2Id: number) { const entry = this.guidMap.get(v2Id) return entry?.type === "DG.Attribute" ? entry.object as ICodapV2Attribute : undefined diff --git a/v3/src/v2/codap-v2-tile-importers.ts b/v3/src/v2/codap-v2-tile-importers.ts index 22e38ff153..bf2d6d2597 100644 --- a/v3/src/v2/codap-v2-tile-importers.ts +++ b/v3/src/v2/codap-v2-tile-importers.ts @@ -1,3 +1,4 @@ +import { IFreeTileInRowOptions } from "../models/document/free-tile-row" import { ISharedModelManager } from "../models/shared/shared-model-manager" import { ITileModel, ITileModelSnapshotIn } from "../models/tiles/tile-model" import { CodapV2Document } from "./codap-v2-document" @@ -7,14 +8,14 @@ import { ICodapV2BaseComponent } from "./codap-v2-types" Tiles that can be imported from components in v2 documents should register their importer functions here. */ - // the arguments passed to a v2 component importer function +export type LayoutTransformFn = (layout: IFreeTileInRowOptions) => IFreeTileInRowOptions export interface V2TileImportArgs { v2Component: ICodapV2BaseComponent v2Document: CodapV2Document sharedModelManager?: ISharedModelManager // function to call to insert the imported tile into the document - insertTile: (tileSnap: ITileModelSnapshotIn) => ITileModel | undefined + insertTile: (tileSnap: ITileModelSnapshotIn, transform?: LayoutTransformFn) => ITileModel | undefined } export type V2TileImportFn = (args: V2TileImportArgs) => ITileModel | undefined diff --git a/v3/src/v2/codap-v2-types.ts b/v3/src/v2/codap-v2-types.ts index 98a38a46be..6d885f8381 100644 --- a/v3/src/v2/codap-v2-types.ts +++ b/v3/src/v2/codap-v2-types.ts @@ -210,7 +210,7 @@ export interface ICodapV2GameContext extends Omit { } // when exporting a v3 data set to v2 data context -type DCNotYetExported = "flexibleGroupingChangeFlag" | "contextStorage" +type DCNotYetExported = "flexibleGroupingChangeFlag" export interface ICodapV2DataContextV3 extends SetOptional, DCNotYetExported> { collections: ICodapV2CollectionV3[] @@ -312,16 +312,8 @@ export interface ICodapV2CaseCardStorage extends ICodapV2BaseComponentStorage { context: IGuidLink<"DG.DataContextRecord"> } title?: string - // TODO_V2_IMPORT isActive is not imported - // it occurs in close to 11,0000 files in cfm-shared - // these are both in table and case card - // it might not be optional isActive?: boolean - // TODO_V2_IMPORT columnWidthPct is not imported - // it occurs 39 times in cfm-shared columnWidthPct?: string - // TODO_V2_IMPORT columnWidthMap is not imported - // it occurs 843 times in cfm-shared columnWidthMap?: Record } @@ -884,12 +876,12 @@ export interface ICodapV2TableComponent extends ICodapV2BaseComponent { export const isV2TableComponent = (component: ICodapV2BaseComponent): component is ICodapV2TableComponent => component.type === "DG.TableView" -// TODO_V2_IMPORT: handle importing case cards: -// https://www.pivotaltracker.com/story/show/188596023 export interface ICodapV2CaseCardComponent extends ICodapV2BaseComponent { type: "DG.CaseCard" componentStorage: ICodapV2CaseCardStorage } +export const isV2CaseCardComponent = (component: ICodapV2BaseComponent): component is ICodapV2CaseCardComponent => + component.type === "DG.CaseCard" // TODO_V2_IMPORT: handle importing images // This is used 3,971 times in cfm-shared diff --git a/v3/src/v2/import-v2-document.ts b/v3/src/v2/import-v2-document.ts index e7bec5c700..05eb41a904 100644 --- a/v3/src/v2/import-v2-document.ts +++ b/v3/src/v2/import-v2-document.ts @@ -4,7 +4,7 @@ import { getTileComponentInfo } from "../models/tiles/tile-component-info" import { getSharedModelManager } from "../models/tiles/tile-environment" import { ITileModel, ITileModelSnapshotIn } from "../models/tiles/tile-model" import { CodapV2Document } from "./codap-v2-document" -import { importV2Component } from "./codap-v2-tile-importers" +import { importV2Component, LayoutTransformFn } from "./codap-v2-tile-importers" import { IFreeTileInRowOptions, isFreeTileRow } from "../models/document/free-tile-row" export function importV2Document(v2Document: CodapV2Document) { @@ -37,7 +37,7 @@ export function importV2Document(v2Document: CodapV2Document) { const row = content?.firstRow let maxZIndex = 0 v2Document.components.forEach(v2Component => { - const insertTile = (tile: ITileModelSnapshotIn) => { + const insertTile = (tile: ITileModelSnapshotIn, transform?: LayoutTransformFn) => { let newTile: ITileModel | undefined if (row && tile) { const info = getTileComponentInfo(tile.content.type) @@ -54,9 +54,10 @@ export function importV2Document(v2Document: CodapV2Document) { const _height = !info?.isFixedHeight ? { height } : {} const _zIndex = zIndex != null ? { zIndex } : {} if (zIndex != null && zIndex > maxZIndex) maxZIndex = zIndex - const layout: IFreeTileInRowOptions = { + const _layout: IFreeTileInRowOptions = { x: left, y: top, ..._width, ..._height, ..._zIndex, isHidden, isMinimized } + const layout = transform?.(_layout) ?? _layout newTile = content?.insertTileSnapshotInRow(tile, row, layout) } }