From 467263e144e471fc8447b0bb495342d83e22ded4 Mon Sep 17 00:00:00 2001 From: Scott Cytacki Date: Tue, 17 Dec 2024 10:51:52 -0500 Subject: [PATCH 1/4] Export game view This makes `isPlugin` a serialized field in V3. That is necessary so we can keep track of which WebViews are v2 GameViews and which are v2 WebViews. The `setIsPlugin` is marked as `withoutUndo` so when a plugin is added and it sets up the iframe phone this change of the document state doesn't add an action to the undo stack. The `contextStorage` field is added to the DataSet conversion. This was necessary to get a document to load with a plugin that had created a data table. However this document is still broken because it data context collections seem to be reversed and disconnected. Name handling in GameViews adds more complexity to the already complex name and title handling. GameViews store their name in componentStorage.currentGameName instead of componentStorage.gameName --- v3/src/components/web-view/web-view-model.ts | 6 +- .../web-view/web-view-registration.test.ts | 56 ++++++++++++++++- .../web-view/web-view-registration.ts | 54 ++++++++++++---- .../data-interactive-type-utils.ts | 7 ++- v3/src/test/v2/game-view-microdata.codap | 61 +++++++++++++++++++ v3/src/v2/codap-v2-types.ts | 6 +- 6 files changed, 167 insertions(+), 23 deletions(-) create mode 100644 v3/src/test/v2/game-view-microdata.codap diff --git a/v3/src/components/web-view/web-view-model.ts b/v3/src/components/web-view/web-view-model.ts index 7d577280fd..02f457228e 100644 --- a/v3/src/components/web-view/web-view-model.ts +++ b/v3/src/components/web-view/web-view-model.ts @@ -2,6 +2,7 @@ import iframePhone from "iframe-phone" import { Instance, SnapshotIn, types } from "mobx-state-tree" import { DIMessage } from "../../data-interactive/iframe-phone-types" import { ITileContentModel, TileContentModel } from "../../models/tiles/tile-content" +import { withoutUndo } from "../../models/history/without-undo" import { kWebViewTileType } from "./web-view-defs" export const kDefaultAllowEmptyAttributeDeletion = true @@ -26,11 +27,11 @@ export const WebViewModel = TileContentModel preventBringToFront: kDefaultPreventBringToFront, preventDataContextReorg: kDefaultPreventDataContextReorg, preventTopLevelReorg: kDefaultPreventTopLevelReorg, - respectEditableItemAttribute: kDefaultRespectEditableItemAttribute + respectEditableItemAttribute: kDefaultRespectEditableItemAttribute, + isPlugin: false }) .volatile(self => ({ dataInteractiveController: undefined as iframePhone.IframePhoneRpcEndpoint | undefined, - isPlugin: false, version: kDefaultWebViewVersion })) .views(self => ({ @@ -43,6 +44,7 @@ export const WebViewModel = TileContentModel self.dataInteractiveController = controller }, setIsPlugin(isPlugin: boolean) { + withoutUndo() self.isPlugin = isPlugin }, setSavedState(state: unknown) { diff --git a/v3/src/components/web-view/web-view-registration.test.ts b/v3/src/components/web-view/web-view-registration.test.ts index 12a119059a..dc07e2a637 100644 --- a/v3/src/components/web-view/web-view-registration.test.ts +++ b/v3/src/components/web-view/web-view-registration.test.ts @@ -8,7 +8,7 @@ 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 { ICodapV2DocumentJson, ICodapV2WebViewStorage } from "../../v2/codap-v2-types" +import { ICodapV2DocumentJson, ICodapV2GameViewStorage, ICodapV2WebViewStorage } from "../../v2/codap-v2-types" import { kWebViewTileType } from "./web-view-defs" import { isWebViewModel } from "./web-view-model" import "./web-view-registration" @@ -50,13 +50,67 @@ describe("WebView registration", () => { expect(tile).toBeDefined() expect(mockInsertTile).toHaveBeenCalledTimes(1) const content = isWebViewModel(tile?.content) ? tile?.content : undefined + expect(tile.name).toBe("https://codap-resources.concord.org/images/walkingrates-50-percent.png") + expect(tile._title).toBe("Walking Rates") expect(getTileContentInfo(kWebViewTileType)!.getTitle(tile)).toBe("Walking Rates") expect(content?.url).toBe("https://codap-resources.concord.org/images/walkingrates-50-percent.png") + expect(content?.isPlugin).toBe(false) const row = docContent.getRowByIndex(0) as IFreeTileRow const componentExport = exportV2Component({ tile, row, sharedModelManager }) expect(componentExport?.type).toBe("DG.WebView") const contentStorage = componentExport?.componentStorage as ICodapV2WebViewStorage expect(contentStorage.URL).toBe("https://codap-resources.concord.org/images/walkingrates-50-percent.png") + expect(contentStorage.name).toBe("https://codap-resources.concord.org/images/walkingrates-50-percent.png") + expect(contentStorage.title).toBe("Walking Rates") + expect(contentStorage.userSetTitle).toBe(true) + }) + + it("imports/exports v2 game view components", () => { + const file = path.join(__dirname, "../../test/v2", "game-view-microdata.codap") + const json = fs.readFileSync(file, "utf8") + const doc = safeJsonParse(json)! + const v2Document = new CodapV2Document(doc) + + 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 tile = importV2Component({ + v2Component: v2Document.components[0], + v2Document, + sharedModelManager, + insertTile: mockInsertTile + })! + expect(tile).toBeDefined() + expect(mockInsertTile).toHaveBeenCalledTimes(1) + const content = isWebViewModel(tile?.content) ? tile?.content : undefined + // Note: a component with a userSetTitle false (like in this case) does not import the + // title into _title. However the name is imported. + // When the _title is undefined the name is used as the title + expect(tile._title).toBeUndefined() + expect(tile.name).toBe("Microdata Portal") + expect(getTileContentInfo(kWebViewTileType)!.getTitle(tile)).toBe("Microdata Portal") + expect(content?.url).toBe("https://codap-resources.concord.org/plugins/sdlc/plugin/index.html") + expect(content?.isPlugin).toBe(true) + + const row = docContent.getRowByIndex(0) as IFreeTileRow + const componentExport = exportV2Component({ tile, row, sharedModelManager }) + expect(componentExport?.type).toBe("DG.GameView") + const contentStorage = componentExport?.componentStorage as ICodapV2GameViewStorage + + expect(contentStorage.currentGameName).toBe("Microdata Portal") + // Note: the value of the exported title can probably be anything here, but undefined seems + // to be a safe value to make it clear to CODAPv2 that user hasn't set the title + expect(contentStorage.title).toBeUndefined() + expect(contentStorage.userSetTitle).toBe(false) + + // Note: we do not convert the URL back to the relative one that is used by CODAPv2 + // this seems OK to do. + expect(contentStorage.currentGameUrl).toBe("https://codap-resources.concord.org/plugins/sdlc/plugin/index.html") }) }) diff --git a/v3/src/components/web-view/web-view-registration.ts b/v3/src/components/web-view/web-view-registration.ts index 8cfdbfe107..1af1691502 100644 --- a/v3/src/components/web-view/web-view-registration.ts +++ b/v3/src/components/web-view/web-view-registration.ts @@ -48,27 +48,53 @@ registerV2TileExporter(kWebViewTileType, ({ tile }) => { // the tile type is kWebViewTileType which is what isWebViewModel is using. const webViewContent = isWebViewModel(tile.content) ? tile.content : undefined const url = webViewContent?.url ?? "" - const v2WebView: V2ExportedComponent = { - type: "DG.WebView", - componentStorage: { - URL: url + if (webViewContent?.isPlugin) { + const v2GameView: V2ExportedComponent = { + type: "DG.GameView", + componentStorage: { + currentGameUrl: url, + // TODO_V2_EXPORT the exportV2Component function which calls this function, automatically adds + // a name property to componentStorage. This name property isn't set by CODAPv2 when saving + // a game view. This extra name field might cause problems in CODAPv2 + currentGameName: tile.name, + savedGameState: webViewContent?.state ?? undefined + // TODO_V2_EXPORT add the rest of the game properties + } } + return v2GameView + } else { + const v2WebView: V2ExportedComponent = { + type: "DG.WebView", + componentStorage: { + URL: url + } + } + return v2WebView } - return v2WebView }) -function addWebViewSnapshot(args: V2TileImportArgs, guid: number, url?: string, state?: unknown) { +function addWebViewSnapshot(args: V2TileImportArgs, name?: string, url?: string, state?: unknown) { const { v2Component, insertTile } = args - const { name, title, userSetTitle } = v2Component.componentStorage || {} + const { guid } = v2Component + const { title, userSetTitle } = v2Component.componentStorage || {} const content: IWebViewSnapshot = { type: kWebViewTileType, state, - url + url, + isPlugin: isV2GameViewComponent(v2Component) } const webViewTileSnap: ITileModelSnapshotIn = { id: toV3Id(kWebViewIdPrefix, guid), name, + // Note: when a game view is imported the userSetTitle is often + // false, and often the title property is set to title which the plugin provided + // to CODAPv2. This value is also set in componentStorage.currentGameName. This + // currentGameName value is imported as the name field above. + // If round tripping a v2 document through v3 the title will be lost because + // of this. The name will be preserved though. Even when the name was not preserved + // CODAPv2 seemed to handle this correctly and updated the the title when the document + // is loaded. _title: (userSetTitle && title) || undefined, content } @@ -81,10 +107,12 @@ function importWebView(args: V2TileImportArgs) { if (!isV2WebViewComponent(v2Component)) return // parse the v2 content - const { guid, componentStorage: { URL } } = v2Component + const { componentStorage: { name, URL } } = v2Component // create webView model - return addWebViewSnapshot(args, guid, URL) + // Note: a renamed WebView has the componentStorage.name set to the URL, + // only the componentStorage.title is updated + return addWebViewSnapshot(args, name, URL) } registerV2TileImporter("DG.WebView", importWebView) @@ -93,10 +121,12 @@ function importGameView(args: V2TileImportArgs) { if (!isV2GameViewComponent(v2Component)) return // parse the v2 content - const { guid, componentStorage: { currentGameUrl, savedGameState} } = v2Component + const { componentStorage: { currentGameUrl, currentGameName, savedGameState} } = v2Component // create webView model - return addWebViewSnapshot(args, guid, processPluginUrl(currentGameUrl), savedGameState) + // Note: a renamed GameView has the componentStorage.currentGameName set to the value + // provided by the plugin, only the componentStorage.title is updated + return addWebViewSnapshot(args, currentGameName, processPluginUrl(currentGameUrl), savedGameState) } registerV2TileImporter("DG.GameView", importGameView) diff --git a/v3/src/data-interactive/data-interactive-type-utils.ts b/v3/src/data-interactive/data-interactive-type-utils.ts index 7bafe74e48..f5cc216e38 100644 --- a/v3/src/data-interactive/data-interactive-type-utils.ts +++ b/v3/src/data-interactive/data-interactive-type-utils.ts @@ -202,10 +202,11 @@ export function convertDataSetToV2(dataSet: IDataSet, exportCases = false): ICod description, // metadata, // preventReorg, - // TODO_V2_EXPORT + // TODO_V2_EXPORT setAsideItems setAsideItems: [], - // TODO_V2_IMPORT, TODO_V2_EXPORT - contextStorage: { _links_: { selectedCases: [] } } + // TODO_V2_EXPORT contextStorage + // providing an empty object makes it possible for CODAPv2 to load more exported documents + contextStorage: {} } } diff --git a/v3/src/test/v2/game-view-microdata.codap b/v3/src/test/v2/game-view-microdata.codap new file mode 100644 index 0000000000..4696c78f54 --- /dev/null +++ b/v3/src/test/v2/game-view-microdata.codap @@ -0,0 +1,61 @@ +{ + "name": "Untitled Document", + "guid": 1, + "id": 1, + "components": [ + { + "type": "DG.GameView", + "guid": 2, + "id": 2, + "componentStorage": { + "currentGameName": "Microdata Portal", + "currentGameUrl": "../../../../extn/plugins/sdlc/plugin/index.html", + "allowInitGameOverride": true, + "savedGameState": { + "sampleNumber": 1, + "sampleSize": 16, + "selectedYears": [ + 2020 + ], + "selectedStates": [], + "selectedAttributes": [ + "Sex", + "Age", + "Year", + "State", + "Boundaries" + ], + "keepExistingData": false, + "activityLog": [ + { + "time": "12/17/2024, 9:08:16 AM", + "message": "Connection: /4g/false/50/10/" + } + ] + }, + "title": "Microdata Portal", + "userSetTitle": false, + "cannotClose": false + }, + "layout": { + "width": 380, + "height": 520, + "left": 5, + "top": 5, + "zIndex": 101, + "isVisible": true, + "right": 385, + "bottom": 525 + }, + "savedHeight": null + } + ], + "contexts": [], + "globalValues": [], + "appName": "DG", + "appVersion": "2.0", + "appBuildNum": "0730", + "lang": "en", + "idCount": 2, + "metadata": {} +} diff --git a/v3/src/v2/codap-v2-types.ts b/v3/src/v2/codap-v2-types.ts index 43a7b9c5d0..2235e387f4 100644 --- a/v3/src/v2/codap-v2-types.ts +++ b/v3/src/v2/codap-v2-types.ts @@ -210,9 +210,8 @@ export interface ICodapV2GameContext extends Omit { } // when exporting a v3 data set to v2 data context -type DCNotYetExported = "flexibleGroupingChangeFlag" export interface ICodapV2DataContextV3 - extends SetOptional, DCNotYetExported> { + extends Omit { collections: ICodapV2CollectionV3[] } @@ -340,9 +339,6 @@ export interface ICodapV2WebViewStorage extends ICodapV2BaseComponentStorage { export interface ICodapV2GameViewStorage extends ICodapV2BaseComponentStorage { currentGameUrl: string savedGameState?: unknown - // TODO_V2_IMPORT currentGameName is not imported - // it occurs in 17,000 files in cfm-shared - // it might not be optional currentGameName?: string // TODO_V2_IMPORT allowInitGameOverride is not imported // it occurs in at least 12,500 files in cfm-shared From 3dec6fea951f841c2fe76019cafcdb50926c2fa2 Mon Sep 17 00:00:00 2001 From: Kirk Swenson Date: Tue, 17 Dec 2024 18:36:02 -0800 Subject: [PATCH 2/4] chore: code review tweak -- v2 exporter functions support options --- .../web-view/web-view-registration.test.ts | 7 ++++--- .../web-view/web-view-registration.ts | 17 ++++++++++------- v3/src/v2/codap-v2-tile-exporters.ts | 15 ++++++++++++--- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/v3/src/components/web-view/web-view-registration.test.ts b/v3/src/components/web-view/web-view-registration.test.ts index dc07e2a637..d18a199169 100644 --- a/v3/src/components/web-view/web-view-registration.test.ts +++ b/v3/src/components/web-view/web-view-registration.test.ts @@ -4,7 +4,7 @@ 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 { hasOwnProperty, 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" @@ -22,7 +22,7 @@ describe("WebView registration", () => { expect(contentInfo).toBeDefined() expect(getTileComponentInfo(kWebViewTileType)).toBeDefined() const defaultContent = contentInfo?.defaultContent() - expect(defaultContent).toBeDefined(); + expect(defaultContent).toBeDefined() }) it("imports/exports v2 web view components", () => { @@ -102,7 +102,8 @@ describe("WebView registration", () => { const componentExport = exportV2Component({ tile, row, sharedModelManager }) expect(componentExport?.type).toBe("DG.GameView") const contentStorage = componentExport?.componentStorage as ICodapV2GameViewStorage - + // shouldn't write out `name` property for GameView components + expect(hasOwnProperty(contentStorage, "name")).toBe(false) expect(contentStorage.currentGameName).toBe("Microdata Portal") // Note: the value of the exported title can probably be anything here, but undefined seems // to be a safe value to make it clear to CODAPv2 that user hasn't set the title diff --git a/v3/src/components/web-view/web-view-registration.ts b/v3/src/components/web-view/web-view-registration.ts index 1af1691502..894f44a1eb 100644 --- a/v3/src/components/web-view/web-view-registration.ts +++ b/v3/src/components/web-view/web-view-registration.ts @@ -7,9 +7,9 @@ import { ITileModelSnapshotIn } from "../../models/tiles/tile-model" import { toV3Id } from "../../utilities/codap-utils" import { t } from "../../utilities/translation/translate" import { registerV2TileImporter, V2TileImportArgs } from "../../v2/codap-v2-tile-importers" -import { registerV2TileExporter, V2ExportedComponent } from "../../v2/codap-v2-tile-exporters" +import { registerV2TileExporter, V2ExportedComponent, V2TileExportFn } from "../../v2/codap-v2-tile-exporters" import { - isV2WebViewComponent, isV2GameViewComponent, ICodapV2WebViewComponent + isV2WebViewComponent, isV2GameViewComponent, ICodapV2WebViewComponent, ICodapV2GameViewComponent } from "../../v2/codap-v2-types" import { kV2GameType, kV2WebViewType, kWebViewTileType } from "./web-view-defs" import { isWebViewModel, IWebViewSnapshot, WebViewModel } from "./web-view-model" @@ -43,7 +43,7 @@ registerTileComponentInfo({ defaultHeight: kDefaultWebViewHeight }) -registerV2TileExporter(kWebViewTileType, ({ tile }) => { +const exportFn: V2TileExportFn = ({ tile }) => { // This really should be a WebView Model. We shouldn't be called unless // the tile type is kWebViewTileType which is what isWebViewModel is using. const webViewContent = isWebViewModel(tile.content) ? tile.content : undefined @@ -53,9 +53,6 @@ registerV2TileExporter(kWebViewTileType, ({ tile }) => { type: "DG.GameView", componentStorage: { currentGameUrl: url, - // TODO_V2_EXPORT the exportV2Component function which calls this function, automatically adds - // a name property to componentStorage. This name property isn't set by CODAPv2 when saving - // a game view. This extra name field might cause problems in CODAPv2 currentGameName: tile.name, savedGameState: webViewContent?.state ?? undefined // TODO_V2_EXPORT add the rest of the game properties @@ -71,7 +68,13 @@ registerV2TileExporter(kWebViewTileType, ({ tile }) => { } return v2WebView } -}) +} +exportFn.options = ({ tile }) => { + const webViewContent = isWebViewModel(tile.content) ? tile.content : undefined + // v2 doesn't write out a `name` property for game view components + return { suppressName: webViewContent?.isPlugin } +} +registerV2TileExporter(kWebViewTileType, exportFn) function addWebViewSnapshot(args: V2TileImportArgs, name?: string, url?: string, state?: unknown) { const { v2Component, insertTile } = args diff --git a/v3/src/v2/codap-v2-tile-exporters.ts b/v3/src/v2/codap-v2-tile-exporters.ts index 1acd700e5a..00a15711b2 100644 --- a/v3/src/v2/codap-v2-tile-exporters.ts +++ b/v3/src/v2/codap-v2-tile-exporters.ts @@ -15,7 +15,13 @@ export interface V2TileExportArgs { row?: IFreeTileRow sharedModelManager?: ISharedModelManager } -export type V2TileExportFn = (args: V2TileExportArgs) => Maybe +interface V2TileExportFnOptions { + suppressName?: boolean +} +interface V2TileExportFnOptionsProp { + options?: (args: V2TileExportArgs) => V2TileExportFnOptions +} +export type V2TileExportFn = ((args: V2TileExportArgs) => Maybe) & V2TileExportFnOptionsProp // map from v2 component type to export function const gV2TileExporters = new Map() @@ -34,13 +40,16 @@ export function registerV2TileExporter(tileType: string, exportFn: V2TileExportF // export the specified v2 component using the appropriate registered exporter export function exportV2Component(args: V2TileExportArgs): Maybe { - const output = gV2TileExporters.get(args.tile.content.type)?.(args) + const v2ExportFn = gV2TileExporters.get(args.tile.content.type) + const suppressName = v2ExportFn?.options?.(args).suppressName + const output = v2ExportFn?.(args) if (!output) return const layout = args.row?.getTileLayout(args.tile.id) if (!isFreeTileLayout(layout)) return const id = toV2Id(args.tile.id) + const name = suppressName ? undefined : { name: args.tile.name } const tileWidth = layout.width ?? kDefaultTileWidth const tileHeight = layout.height ?? kDefaultTileHeight @@ -50,7 +59,7 @@ export function exportV2Component(args: V2TileExportArgs): Maybe Date: Fri, 20 Dec 2024 10:45:36 -0800 Subject: [PATCH 3/4] chore: replace `isPlugin` property with `subType` property --- .../web-view/component-handler-web-view.test.ts | 8 ++++---- .../web-view/use-data-interactive-controller.ts | 2 +- v3/src/components/web-view/web-view-defs.ts | 3 +++ v3/src/components/web-view/web-view-model.ts | 16 +++++++++++----- .../components/web-view/web-view-registration.ts | 12 +++++++++--- 5 files changed, 28 insertions(+), 13 deletions(-) diff --git a/v3/src/components/web-view/component-handler-web-view.test.ts b/v3/src/components/web-view/component-handler-web-view.test.ts index f56e138a2e..7430362fe9 100644 --- a/v3/src/components/web-view/component-handler-web-view.test.ts +++ b/v3/src/components/web-view/component-handler-web-view.test.ts @@ -75,8 +75,8 @@ describe("DataInteractive ComponentHandler WebView and Game", () => { expect(tileLayout?.width).toBe(newValue) // Create game with url - const multidataUrl = "https://codap.concord.org/multidata-plugin/" - const result3 = handler.create!({}, { type: "game", URL: multidataUrl }) + const multiDataUrl = "https://codap.concord.org/multidata-plugin/" + const result3 = handler.create!({}, { type: "game", URL: multiDataUrl }) expect(result3.success).toBe(true) expect(documentContent.tileMap.size).toBe(2) const result3Values = result3.values as DIComponentInfo @@ -84,10 +84,10 @@ describe("DataInteractive ComponentHandler WebView and Game", () => { expect(tile3).toBeDefined() expect(isWebViewModel(tile3.content)).toBe(true) const gameModel = tile3.content as IWebViewModel - expect(gameModel.url).toBe(multidataUrl) + expect(gameModel.url).toBe(multiDataUrl) // Get game - gameModel.setIsPlugin(true) // This would normally be set automatically when the plugin connects to codap + gameModel.setSubType("plugin") // This would normally be set automatically when the plugin connects to codap testGetComponent(tile3, handler, (gameTile, values) => { const { URL } = values as V2Game expect(URL).toBe((gameTile.content as IWebViewModel).url) diff --git a/v3/src/components/web-view/use-data-interactive-controller.ts b/v3/src/components/web-view/use-data-interactive-controller.ts index 84352b9663..75a0cd2269 100644 --- a/v3/src/components/web-view/use-data-interactive-controller.ts +++ b/v3/src/components/web-view/use-data-interactive-controller.ts @@ -36,7 +36,7 @@ export function useDataInteractiveController(iframeRef: React.RefObject { - webViewModel?.setIsPlugin(true) + webViewModel?.setSubType("plugin") debugLog(DEBUG_PLUGINS, "connection with iframe established") }) const handler: iframePhone.IframePhoneRpcEndpointHandlerFn = diff --git a/v3/src/components/web-view/web-view-defs.ts b/v3/src/components/web-view/web-view-defs.ts index 7f47b99c10..40f92d1699 100644 --- a/v3/src/components/web-view/web-view-defs.ts +++ b/v3/src/components/web-view/web-view-defs.ts @@ -4,3 +4,6 @@ export const kV2GuideViewType = "guideView" export const kV2WebViewType = "webView" export const kWebViewTileClass = "codap-web-view" export const kWebViewBodyClass = "codap-web-view-body" + +export const webViewSubTypes = ["guide", "plugin"] as const +export type WebViewSubType = typeof webViewSubTypes[number] diff --git a/v3/src/components/web-view/web-view-model.ts b/v3/src/components/web-view/web-view-model.ts index 02f457228e..27a69d2bee 100644 --- a/v3/src/components/web-view/web-view-model.ts +++ b/v3/src/components/web-view/web-view-model.ts @@ -3,7 +3,7 @@ import { Instance, SnapshotIn, types } from "mobx-state-tree" import { DIMessage } from "../../data-interactive/iframe-phone-types" import { ITileContentModel, TileContentModel } from "../../models/tiles/tile-content" import { withoutUndo } from "../../models/history/without-undo" -import { kWebViewTileType } from "./web-view-defs" +import { kWebViewTileType, WebViewSubType, webViewSubTypes } from "./web-view-defs" export const kDefaultAllowEmptyAttributeDeletion = true export const kDefaultBlockAPIRequestsWhileEditing = false @@ -18,6 +18,7 @@ export const WebViewModel = TileContentModel .named("WebViewModel") .props({ type: types.optional(types.literal(kWebViewTileType), kWebViewTileType), + subType: types.maybe(types.enumeration(webViewSubTypes)), url: "", state: types.frozen(), // fields controlled by plugins (like Collaborative) via interactiveFrame requests @@ -27,8 +28,7 @@ export const WebViewModel = TileContentModel preventBringToFront: kDefaultPreventBringToFront, preventDataContextReorg: kDefaultPreventDataContextReorg, preventTopLevelReorg: kDefaultPreventTopLevelReorg, - respectEditableItemAttribute: kDefaultRespectEditableItemAttribute, - isPlugin: false + respectEditableItemAttribute: kDefaultRespectEditableItemAttribute }) .volatile(self => ({ dataInteractiveController: undefined as iframePhone.IframePhoneRpcEndpoint | undefined, @@ -37,15 +37,21 @@ export const WebViewModel = TileContentModel .views(self => ({ get allowBringToFront() { return !self.preventBringToFront + }, + get isGuide() { + return self.subType === "guide" + }, + get isPlugin() { + return self.subType === "plugin" } })) .actions(self => ({ setDataInteractiveController(controller?: iframePhone.IframePhoneRpcEndpoint) { self.dataInteractiveController = controller }, - setIsPlugin(isPlugin: boolean) { + setSubType(subType: WebViewSubType) { withoutUndo() - self.isPlugin = isPlugin + self.subType = subType }, setSavedState(state: unknown) { self.state = state diff --git a/v3/src/components/web-view/web-view-registration.ts b/v3/src/components/web-view/web-view-registration.ts index 894f44a1eb..c8645183a4 100644 --- a/v3/src/components/web-view/web-view-registration.ts +++ b/v3/src/components/web-view/web-view-registration.ts @@ -11,7 +11,7 @@ import { registerV2TileExporter, V2ExportedComponent, V2TileExportFn } from "../ import { isV2WebViewComponent, isV2GameViewComponent, ICodapV2WebViewComponent, ICodapV2GameViewComponent } from "../../v2/codap-v2-types" -import { kV2GameType, kV2WebViewType, kWebViewTileType } from "./web-view-defs" +import { kV2GameType, kV2WebViewType, kWebViewTileType, WebViewSubType } from "./web-view-defs" import { isWebViewModel, IWebViewSnapshot, WebViewModel } from "./web-view-model" import { WebViewComponent } from "./web-view" import { WebViewInspector } from "./web-view-inspector" @@ -80,12 +80,16 @@ function addWebViewSnapshot(args: V2TileImportArgs, name?: string, url?: string, const { v2Component, insertTile } = args const { guid } = v2Component const { title, userSetTitle } = v2Component.componentStorage || {} + const subTypeMap: Record = { + "DG.GameView": "plugin", + "DG.GuideView": "guide" + } const content: IWebViewSnapshot = { type: kWebViewTileType, + subType: subTypeMap[v2Component.type], state, - url, - isPlugin: isV2GameViewComponent(v2Component) + url } const webViewTileSnap: ITileModelSnapshotIn = { id: toV3Id(kWebViewIdPrefix, guid), @@ -133,6 +137,8 @@ function importGameView(args: V2TileImportArgs) { } registerV2TileImporter("DG.GameView", importGameView) +// TODO add importer for DG.GuideView + const webViewComponentHandler: DIComponentHandler = { create({ values }) { const { URL } = values as V2WebView From ad52a2d524b8aea733033f569372e1b0171e56ed Mon Sep 17 00:00:00 2001 From: Kirk Swenson Date: Fri, 20 Dec 2024 11:08:53 -0800 Subject: [PATCH 4/4] chore: code review tweaks --- v3/src/components/web-view/web-view-model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v3/src/components/web-view/web-view-model.ts b/v3/src/components/web-view/web-view-model.ts index 27a69d2bee..81c95172a7 100644 --- a/v3/src/components/web-view/web-view-model.ts +++ b/v3/src/components/web-view/web-view-model.ts @@ -49,7 +49,7 @@ export const WebViewModel = TileContentModel setDataInteractiveController(controller?: iframePhone.IframePhoneRpcEndpoint) { self.dataInteractiveController = controller }, - setSubType(subType: WebViewSubType) { + setSubType(subType?: WebViewSubType) { withoutUndo() self.subType = subType },