Skip to content

Commit

Permalink
187458550 v3 DI Initial Component Requests (#1225)
Browse files Browse the repository at this point in the history
  • Loading branch information
tealefristoe authored Apr 24, 2024
1 parent 4773826 commit 83541a8
Show file tree
Hide file tree
Showing 12 changed files with 169 additions and 10 deletions.
1 change: 0 additions & 1 deletion v3/cypress/e2e/case-card.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { TableTileElements as table } from "../support/elements/table-tile"

beforeEach(() => {
// cy.scrollTo() doesn't work as expected with `scroll-behavior: smooth`
Expand Down
46 changes: 46 additions & 0 deletions v3/cypress/e2e/plugin.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ComponentElements as c } from "../support/elements/component-elements"
import { SliderTileElements as slider } from "../support/elements/slider-tile"
import { TableTileElements as table } from "../support/elements/table-tile"
import { ToolbarElements as toolbar } from "../support/elements/toolbar-elements"
Expand All @@ -15,6 +16,7 @@ context("codap plugins", () => {
webView.enterUrl(url)
cy.wait(1000)
}

it('will handle plugin requests', () => {
openAPITester()

Expand Down Expand Up @@ -95,6 +97,50 @@ context("codap plugins", () => {
webView.confirmAPITesterResponseContains(/"values":\s\[\s{\s"name":\s*"Mammals"/)
webView.clearAPITesterResponses()
})

it('will handle component related requests', () => {
openAPITester()

cy.log("Handle get componentList request")
const cmd1 = `{
"action": "get",
"resource": "componentList"
}`
webView.sendAPITesterCommand(cmd1)
webView.confirmAPITesterResponseContains(/"success":\s*true/)
webView.getAPITesterResponse().then((value: any) => {
// Find the id of the table component
const response = JSON.parse(value.eq(1).text())
const tableInfo = response.values.find((info: any) => info.type === "caseTable")
const tableId = tableInfo.id

cy.log("Select component using notify component command")
c.checkComponentFocused("table", false)
const cmd2 = `{
"action": "notify",
"resource": "component[${tableId}]",
"values": {
"request": "select"
}
}`
webView.clearAPITesterResponses()
webView.sendAPITesterCommand(cmd2, cmd1)
webView.confirmAPITesterResponseContains(/"success":\s*true/)
webView.clearAPITesterResponses()
c.checkComponentFocused("table")

cy.log("Delete component using delete component command")
const cmd3 = `{
"action": "delete",
"resource": "component[${tableId}]"
}`
webView.sendAPITesterCommand(cmd3, cmd2)
webView.confirmAPITesterResponseContains(/"success":\s*true/)
webView.clearAPITesterResponses()
c.checkComponentDoesNotExist("table")
})
})

it('will broadcast notifications', () => {
openAPITester()
webView.toggleAPITesterFilter()
Expand Down
7 changes: 7 additions & 0 deletions v3/cypress/support/elements/component-elements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ export const ComponentElements = {
return element.eq(index)
})
},
getComponentTitleBar(component, index = 0) {
return this.getComponentTile(component, index).find(".component-title-bar")
},
checkComponentFocused(component, focused = true, index = 0) {
const check = `${focused ? "" : "not."}have.class`
this.getComponentTitleBar(component, index).should(check, "focusTile")
},
getComponentTitle(component, index = 0) {
return this.getComponentTile(component, index).find("[data-testid=editable-component-title]")
},
Expand Down
5 changes: 4 additions & 1 deletion v3/cypress/support/elements/web-view-tile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,11 @@ export const WebViewTileElements = {
WebViewTileElements.getIFrame().find(`.di-message-area`).type(command)
WebViewTileElements.getIFrame().find(`.di-send-button`).click()
},
getAPITesterResponse() {
return WebViewTileElements.getIFrame().find(`.di-log-message`)
},
confirmAPITesterResponseContains(response: string | RegExp) {
WebViewTileElements.getIFrame().find(`.di-log-message`).contains(response).should("exist")
WebViewTileElements.getAPITesterResponse().contains(response).should("exist")
},
toggleAPITesterFilter() {
WebViewTileElements.getIFrame().find(`#filter-button`).click()
Expand Down
31 changes: 31 additions & 0 deletions v3/src/data-interactive/data-interactive-component-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { kCalculatorTileType } from "../components/calculator/calculator-defs"
import { kCaseTableTileType } from "../components/case-table/case-table-defs"
import { kGraphTileType } from "../components/graph/graph-defs"
import { kMapTileType } from "../components/map/map-defs"
import { kSliderTileType } from "../components/slider/slider-defs"
import { kWebViewTileType } from "../components/web-view/web-view-defs"

export const kV2CalculatorType = "calculator"
export const kV2CaseTableType = "caseTable"
export const kV2CaseCardType = "caseCard"
export const kV2GameType = "game"
export const kV2GraphType = "graph"
export const kV2GuideViewType = "guideView"
export const kV2ImageType = "image"
export const kV2MapType = "map"
export const kV2SliderType = "slider"
export const kV2TextType = "text"
export const kV2WebViewType = "webView"

export const kComponentTypeV3ToV2Map: Record<string, string> = {
[kCalculatorTileType]: kV2CalculatorType,
[kCaseTableTileType]: kV2CaseTableType,
// kV2CaseCardType
[kGraphTileType]: kV2GraphType,
// kV2GuideViewType
// kV2ImageType
[kMapTileType]: kV2MapType,
[kSliderTileType]: kV2SliderType,
// kV2TextType
[kWebViewTileType]: kV2WebViewType
}
15 changes: 12 additions & 3 deletions v3/src/data-interactive/data-interactive-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,13 @@ export interface DICase {
itemID?: string
}
export type DICollection = Partial<ICodapV2Collection>
export type DIComponent = unknown
export type DIComponent = ITileModel
export interface DIComponentInfo {
id?: string
name?: string
title?: string
type?: string
}
export interface DIGlobal {
name?: string
value?: number
Expand All @@ -58,6 +64,9 @@ export interface DINewCase {
id?: string
itemID?: string
}
export interface DINotification {
request?: string
}

export interface DIResources {
attribute?: IAttribute
Expand All @@ -82,12 +91,12 @@ export interface DIResources {

// types for values accepted as inputs by the API
export type DISingleValues = DIAttribute | DICase | DIDataContext |
DIGlobal | DIInteractiveFrame | DINewCase
DIGlobal | DIInteractiveFrame | DINewCase | DINotification
export type DIValues = DISingleValues | DISingleValues[] | number | string[]

// types returned as outputs by the API
export type DIResultAttributes = { attrs: ICodapV2AttributeV3[] }
export type DIResultSingleValues = DICase | DIGlobal | DIInteractiveFrame
export type DIResultSingleValues = DICase | DIComponentInfo | DIGlobal | DIInteractiveFrame
export type DIResultValues = DIResultSingleValues | DIResultSingleValues[] |
DIAllCases | DIResultAttributes | number

Expand Down
34 changes: 34 additions & 0 deletions v3/src/data-interactive/handlers/component-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { t } from "../../utilities/translation/translate"
import { registerDIHandler } from "../data-interactive-handler"
import { DIHandler, DINotification, DIResources, DIValues } from "../data-interactive-types"
import { uiState } from "../../models/ui-state"
import { appState } from "../../models/app-state"

const componentNotFoundResult = { success: false, values: { error: t("V3.DI.Error.componentNotFound") } } as const

export const diComponentHandler: DIHandler = {
delete(resources: DIResources) {
const { component } = resources
if (!component) return componentNotFoundResult

appState.document.deleteTile(component.id)

return { success: true }
},
notify(resources: DIResources, values?: DIValues) {
const { component } = resources
if (!component) return componentNotFoundResult

if (!values) return { success: false, values: { error: t("V3.DI.Error.valuesRequired") } }

const { request } = values as DINotification
if (request === "select") {
uiState.setFocusedTile(component.id)
// } else if (request === "autoScale") { // TODO Handle autoScale requests
}

return { success: true }
}
}

registerDIHandler("component", diComponentHandler)
26 changes: 26 additions & 0 deletions v3/src/data-interactive/handlers/component-list-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { isWebViewModel } from "../../components/web-view/web-view-model"
import { appState } from "../../models/app-state"
import { kComponentTypeV3ToV2Map, kV2GameType, kV2WebViewType } from "../data-interactive-component-types"
import { registerDIHandler } from "../data-interactive-handler"
import { DIComponentInfo, DIHandler, DIResources } from "../data-interactive-types"

export const diComponentListHandler: DIHandler = {
get(_resources: DIResources) {
const values: DIComponentInfo[] = []
appState.document.content?.tileMap.forEach(tile => {
// TODO Should we add names to tiles?
// TODO Tiles sometimes show titles different than tile.title. Should we return those?
const { content, id, title } = tile
const type = isWebViewModel(content)
? content.isPlugin
? kV2GameType
: kV2WebViewType
: kComponentTypeV3ToV2Map[content.type]
values.push({ id, title, type })
})

return { success: true, values }
}
}

registerDIHandler("componentList", diComponentListHandler)
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const diInteractiveFrameHandler: DIHandler = {
const values: DIInteractiveFrame = {
dimensions,
externalUndoAvailable: true,
id: interactiveFrame.id,
name: interactiveFrame.title,
preventBringToFront: false,
preventDataContextReorg: false,
Expand Down
2 changes: 2 additions & 0 deletions v3/src/data-interactive/register-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import "./handlers/all-cases-handler"
import "./handlers/attribute-handler"
import "./handlers/case-handler"
import "./handlers/case-count-handler"
import "./handlers/component-handler"
import "./handlers/component-list-handler"
import "./handlers/data-context-handler"
import "./handlers/data-context-list-handler"
import "./handlers/global-handler"
Expand Down
9 changes: 4 additions & 5 deletions v3/src/data-interactive/resource-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,10 @@ export function resolveResources(

const dataContext = result.dataContext

// if (resourceSelector.component) {
// result.component = document.getComponentByName(resourceSelector.component)
// || (!isNaN(Number(resourceSelector.component))
// && document.getComponentByID(resourceSelector.component))
// }
if (resourceSelector.component) {
// TODO Get tile by name?
result.component = document.content?.getTile(resourceSelector.component)
}

if (resourceSelector.global) {
const globalManager = document.content?.getFirstSharedModelByType(GlobalValueManager)
Expand Down
2 changes: 2 additions & 0 deletions v3/src/utilities/translation/lang/en-US.json5
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,15 @@
"V3.DI.Error.fieldRequired": "%@1 %@2: %@3 required",
"V3.DI.Error.dataContextNotFound": "DataContext not found",
"V3.DI.Error.collectionNotFound": "Collection not found",
"V3.DI.Error.componentNotFound": "Component not found",
"V3.DI.Error.globalCreation": "error creating global value",
"V3.DI.Error.globalDuplicateName": "globals must have unique names",
"V3.DI.Error.globalIllegalValue": "global values must be numbers",
"V3.DI.Error.globalNotFound": "missing global or value",
"V3.DI.Error.interactiveFrameNotFound": "Interactive Frame not found",
"V3.DI.Error.notFound": "Not found",
"V3.DI.Error.selectionListIllegalValues": "%@1 selectionList requires a list of case IDs.",
"V3.DI.Error.valuesRequired": "A values object is required for this request.",

// CFM/File menu
"DG.fileMenu.menuItem.newDocument": "New",
Expand Down

0 comments on commit 83541a8

Please sign in to comment.