Skip to content

Commit

Permalink
feat: import/export case card from/to v2 documents (#1691)
Browse files Browse the repository at this point in the history
  • Loading branch information
kswenson authored Dec 18, 2024
1 parent 73e9dd7 commit 1db08e0
Show file tree
Hide file tree
Showing 10 changed files with 1,038 additions and 50 deletions.
3 changes: 2 additions & 1 deletion v3/src/components/case-card/case-card-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => ({
Expand All @@ -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)
}
Expand Down
75 changes: 75 additions & 0 deletions v3/src/components/case-card/case-card-registration.test.ts
Original file line number Diff line number Diff line change
@@ -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<ICodapV2DocumentJson>(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)
})

})
103 changes: 74 additions & 29 deletions v3/src/components/case-card/case-card-registration.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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<SetOptional<ICodapV2CaseCardStorage, keyof ICodapV2BaseComponentStorage>>
const dataSet = cardContent?.data
const columnWidthMap: Record<string, number> = {}
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<ICaseCardSnapshot, "columnWidths"> = {
const content: SetRequired<ICaseCardSnapshot, "attributeColumnWidths"> = {
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
Expand All @@ -77,4 +123,3 @@ registerV2TileImporter("DG.CardView", ({ v2Component, v2Document, sharedModelMan

return cardTile
})
*/
6 changes: 4 additions & 2 deletions v3/src/components/case-table/case-table-registration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions v3/src/data-interactive/data-interactive-type-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [] } }
}
}

Expand Down
Loading

0 comments on commit 1db08e0

Please sign in to comment.