From cc4987c90e05c119b83a4175af4cf415d79a1bde Mon Sep 17 00:00:00 2001 From: Teale Fristoe Date: Fri, 23 Aug 2024 17:23:29 -0700 Subject: [PATCH] 188150453 v3 DI Handle Slate Objects in API Requests (#1421) * Start accepting objects rather than strings for text values. --------- Co-authored-by: Kirk Swenson --- v3/src/components/text/text-model.ts | 8 ++- .../components/text/text-registration.test.ts | 52 +++++++++++++++++++ v3/src/components/text/text-registration.ts | 22 ++++---- v3/src/components/text/text-tile.tsx | 8 +-- .../data-interactive-component-types.ts | 3 +- 5 files changed, 78 insertions(+), 15 deletions(-) diff --git a/v3/src/components/text/text-model.ts b/v3/src/components/text/text-model.ts index 160814d4b..14dff34e1 100644 --- a/v3/src/components/text/text-model.ts +++ b/v3/src/components/text/text-model.ts @@ -35,8 +35,14 @@ export const TextModel = TileContentModel })) .actions(self => ({ setValue(value: EditorValue) { - self.isSettingValue = true self.value = editorValueToModelValue(value) + } + })) + .actions(self => ({ + setValueFromEditor(value: EditorValue) { + // The component will not update when isSettingValue is true + self.isSettingValue = true + self.setValue(value) self.isSettingValue = false } })) diff --git a/v3/src/components/text/text-registration.test.ts b/v3/src/components/text/text-registration.test.ts index a631a9e6d..53093b857 100644 --- a/v3/src/components/text/text-registration.test.ts +++ b/v3/src/components/text/text-registration.test.ts @@ -140,4 +140,56 @@ describe("text component handler", () => { documentContent.deleteTile(tileId) expect(documentContent.tileMap.size).toBe(0) }) + + it("can create and then update a text tile with slate type text content and retrieve its contents", () => { + expect(documentContent.tileMap.size).toBe(0) + // creating empty text tile + const emptyTextTileResult = diHandler.create!({}, { + type: "text", + text: { + object: "value", + document: { + children: [{ + type: "paragraph", + children: [{ + text: "To be, or not to be" + }] + }] + } + } + }) + expect(emptyTextTileResult.success).toBe(true) + expect(documentContent.tileMap.size).toBe(1) + const [tileId, tile] = Array.from(documentContent.tileMap.entries())[0] + expect(isTextModel(tile.content)).toBe(true) + expect((tile.content as ITextModel).textContent).toBe("To be, or not to be") + + testGetComponent(tile, diHandler, (textTile, values) => { + const { text } = values as V2Text + expect(isTextModel(textTile.content)).toBe(true) + const textContent = textTile.content as ITextModel + expect(textContent.value).toBe(text) + }) + + const newValues: Partial = { + text: { + object: "value", + document: { + children: [{ + type: "paragraph", + children: [{ + text: "To be, or not to be, that is the question." + }] + }] + } + } + } + testUpdateComponent(tile, diHandler, newValues, (textTile, values) => { + expect(isTextModel(tile.content)).toBe(true) + expect((tile.content as ITextModel).textContent).toBe("To be, or not to be, that is the question.") + }) + + documentContent.deleteTile(tileId) + expect(documentContent.tileMap.size).toBe(0) + }) }) diff --git a/v3/src/components/text/text-registration.ts b/v3/src/components/text/text-registration.ts index 83564c599..0649d0b43 100644 --- a/v3/src/components/text/text-registration.ts +++ b/v3/src/components/text/text-registration.ts @@ -1,4 +1,4 @@ -import { textToSlate } from "@concord-consortium/slate-editor" +import { SlateExchangeValue, textToSlate } from "@concord-consortium/slate-editor" import { SetRequired } from "type-fest" import { V2Text } from "../../data-interactive/data-interactive-component-types" import { registerComponentHandler } from "../../data-interactive/handlers/component-handler" @@ -45,14 +45,17 @@ registerTileComponentInfo({ defaultHeight: 100 }) -function importTextToModelValue(text?: string) { +function importTextToModelValue(text?: string | SlateExchangeValue) { // According to a comment in the v2 code: "Prior to build 0535 this was simple text. // As of 0535 it is a JSON representation of the rich text content." // For v3, we make sure we're always dealing with rich-text JSON. - const json = safeJsonParse(text) - return text != null && json != null && typeof json === "object" - ? text - : editorValueToModelValue(textToSlate(text ?? "")) + if (typeof text === "string") { + const json = safeJsonParse(text) + return text != null && json != null && typeof json === "object" + ? text + : editorValueToModelValue(textToSlate(text)) + } + return editorValueToModelValue(text?.document?.children ?? []) } registerV2TileImporter("DG.TextView", ({ v2Component, insertTile }) => { @@ -91,9 +94,10 @@ registerComponentHandler(kV2TextType, { update(content, values) { if (isTextModel(content)) { const { text } = values as V2Text - if (text) { - const modelValue = importTextToModelValue(text) - content.setValue(modelValueToEditorValue(modelValue)) + if (typeof text === "string") { + content.setValue(modelValueToEditorValue(importTextToModelValue(text))) + } else if (text?.document?.children) { + content.setValue(text.document.children) } } diff --git a/v3/src/components/text/text-tile.tsx b/v3/src/components/text/text-tile.tsx index 30cf9f42b..e004994fe 100644 --- a/v3/src/components/text/text-tile.tsx +++ b/v3/src/components/text/text-tile.tsx @@ -18,14 +18,15 @@ export const TextTile = observer(function TextTile({ tile }: ITileBaseProps) { const textOnFocus = useRef("") const [initialValue, setInitialValue] = useState(() => modelValueToEditorValue(textModel?.value)) + // changes to this value trigger a remount of the slate editor + const mountKey = useRef(0) const editor = useMemo(() => { // slate doesn't have a convenient API for replacing the value in an existing editor, // so we create a new editor instance when the value changes externally (e.g. undo/redo). initialValue // eslint-disable-line no-unused-expressions + ++mountKey.current return createEditor() }, [initialValue]) - // changes to this value trigger a remount of the slate editor - const mountKey = useRef(0) useEffect(() => { return tile && mstReaction( @@ -48,7 +49,6 @@ export const TextTile = observer(function TextTile({ tile }: ITileBaseProps) { if (!textModel.isSettingValue) { // set the new value and remount the editor setInitialValue(modelValueToEditorValue(textModel?.value)) - ++mountKey.current } } })) @@ -67,7 +67,7 @@ export const TextTile = observer(function TextTile({ tile }: ITileBaseProps) { if (textModel && !textModel.isEquivalent(editor.children)) { const textDidChange = textOnFocus.current !== textModel.textContent textModel?.applyModelChange(() => { - textModel.setValue(editor.children) + textModel.setValueFromEditor(editor.children) }, { // log only when the text actually changed, e.g. not on style changes // Note that logging of text changes was commented out in v2 in build 0601. ¯\_(ツ)_/¯ diff --git a/v3/src/data-interactive/data-interactive-component-types.ts b/v3/src/data-interactive/data-interactive-component-types.ts index f4370e29f..fa32a373f 100644 --- a/v3/src/data-interactive/data-interactive-component-types.ts +++ b/v3/src/data-interactive/data-interactive-component-types.ts @@ -1,3 +1,4 @@ +import { SlateExchangeValue } from "@concord-consortium/slate-editor" import { kCalculatorTileType, kV2CalculatorType } from "../components/calculator/calculator-defs" import { kCaseCardTileType, kV2CaseCardType } from "../components/case-card/case-card-defs" import { kCaseTableTileType, kV2CaseTableType } from "../components/case-table/case-table-defs" @@ -113,7 +114,7 @@ export interface V2GetSlider extends V2Slider { value?: number } export interface V2Text extends V2Component { - text?: string + text?: string | SlateExchangeValue type: "text" } export interface V2WebView extends V2Component {