diff --git a/public/static/xblock/client-v0.js b/public/static/xblock/client-v0.js index bd952ff617..12df42f6c6 100644 --- a/public/static/xblock/client-v0.js +++ b/public/static/xblock/client-v0.js @@ -174,14 +174,12 @@ function toVdom(element, nodeName) { * e.g. `const { learnerAnswer } = useFields(props);` */ function useFields(props) { - var parts = [props['system-fields'], props['content-fields'], props['user-fields']]; - return q(function () { - var fields = {}; - for (var _i = 0, parts_1 = parts; _i < parts_1.length; _i++) { - var part = parts_1[_i]; - var parsed = JSON.parse(part); - for (var _a = 0, _b = Object.entries(parsed); _a < _b.length; _a++) { - var _c = _b[_a], key = _c[0], value = _c[1]; + const parts = [props['content-fields'], props['user-fields']]; + return q(() => { + const fields = {}; + for (const part of parts) { + const parsed = JSON.parse(part); + for (const [key, value] of Object.entries(parsed)) { fields[key] = value; } } @@ -193,25 +191,42 @@ function useFields(props) { * @param usageKey The usage key, available as an attribute/prop on the web component. */ function getDomElement(usageKey) { - var result = document.querySelector(".xblock-component.xblock-v2[usage-key=\"".concat(usageKey, "\"]")); + const result = document.querySelector(`.xblock-component.xblock-v2[usage-key="${usageKey}"]`); if (result === null) { - throw new Error("Unable to find DOM element for ".concat(usageKey)); + throw new Error(`Unable to find DOM element for ${usageKey}`); } return result; } function getRenderContext(usageKey) { - var result = getDomElement(usageKey).closest('xblock-render-context'); + const result = getDomElement(usageKey).closest('xblock-render-context'); if (result === null) { - throw new Error("XBlock was rendered outside of an : ".concat(usageKey)); + throw new Error(`XBlock was rendered outside of an : ${usageKey}`); } return result; } -function callHandler(usageKey, handlerName, body, method) { - if (method === void 0) { method = 'POST'; } - return getRenderContext(usageKey).callHandler(usageKey, handlerName, body, method); +function callHandlerRaw(usageKey, handlerName, body, method = 'post') { + return getRenderContext(usageKey).callHandlerRaw(usageKey, handlerName, body, method); +} +/** Call a JSON handler using POST. Most handler calls should use this. */ +async function callHandler(usageKey, handlerName, body = {}) { + const response = await callHandlerRaw(usageKey, handlerName, new Blob([JSON.stringify(body)]), 'post'); + const { data, updated_fields } = await response.json(); + const domElement = getDomElement(usageKey); + // Update the actual web component DOM element with any changed field values, if applicable: + for (const fieldScope of ["user", "content"]) { + if (Object.keys(updated_fields[fieldScope]).length > 0) { + const attributeName = `${fieldScope}-fields`; + const scopeFieldValues = JSON.parse(domElement.getAttribute(attributeName)); + for (const [key, value] of Object.entries(updated_fields[fieldScope])) { + scopeFieldValues[key] = value; + } + domElement.setAttribute(attributeName, JSON.stringify(scopeFieldValues)); + } + } + return data; } function registerPreactXBlock(componentClass, blockType, options) { - register(componentClass, "xblock2-".concat(blockType), ['content-fields', 'user-fields'], options); + register(componentClass, `xblock2-${blockType}`, ['content-fields', 'user-fields'], options); } -export { b$1 as Component, k$1 as Fragment, callHandler, E as cloneElement, G as createContext, _$1 as createElement, m$2 as createRef, getDomElement, _$1 as h, m as html, D$1 as hydrate, t$2 as isValidElement, l$1 as options, registerPreactXBlock, B$1 as render, H as toChildArray, x as useCallback, P as useContext, V as useDebugValue, _ as useEffect, b as useErrorBoundary, useFields, g as useId, T as useImperativeHandle, A as useLayoutEffect, q as useMemo, y as useReducer, F as useRef, p as useState }; +export { b$1 as Component, k$1 as Fragment, callHandler, callHandlerRaw, E as cloneElement, G as createContext, _$1 as createElement, m$2 as createRef, getDomElement, _$1 as h, m as html, D$1 as hydrate, t$2 as isValidElement, l$1 as options, registerPreactXBlock, B$1 as render, H as toChildArray, x as useCallback, P as useContext, V as useDebugValue, _ as useEffect, b as useErrorBoundary, useFields, g as useId, T as useImperativeHandle, A as useLayoutEffect, q as useMemo, y as useReducer, F as useRef, p as useState }; diff --git a/src/courseware/component/XBlock.tsx b/src/courseware/component/XBlock.tsx index 15d75cb411..2f70bdfa00 100644 --- a/src/courseware/component/XBlock.tsx +++ b/src/courseware/component/XBlock.tsx @@ -12,7 +12,6 @@ export const XBlock: React.FC = ({ id, ...props }) => { class: 'xblock-component xblock-v2', // For web components in React, the prop is 'class', not 'className' 'usage-key': id, 'content-fields': JSON.stringify(props.contentFields), - 'system-fields': JSON.stringify(props.systemFields), 'user-fields': JSON.stringify(props.userFields), }; diff --git a/src/courseware/component/XBlockRenderContext.tsx b/src/courseware/component/XBlockRenderContext.tsx index a712c7a6fa..23ada7ee8d 100644 --- a/src/courseware/component/XBlockRenderContext.tsx +++ b/src/courseware/component/XBlockRenderContext.tsx @@ -1,5 +1,5 @@ import React from "react"; -import type { AxiosInstance } from "axios"; +import { AxiosHeaders, type AxiosInstance } from "axios"; import { getConfig } from "@edx/frontend-platform"; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; @@ -9,25 +9,20 @@ if (!globalStatus._loadedBlockTypes) { } // TODO: HandlerResponse and CallHandler type definitions should be in a 'xblock-client-api' package on NPM? -export interface HandlerResponse { - data: Record; - updatedFields: Record; -} - -export interface CallHandler { +interface CallHandlerRaw { ( usageKey: string, handlerName: string, - body: Record | ReadableStream | Blob, - method?: 'GET'|'POST', - ): Promise; + body: Blob, + method?: 'get'|'post'|'put'|'delete', + ): Promise; } export function ensureBlockScript(blockType: string): void { if (!globalStatus._loadedBlockTypes.has(blockType)) { globalStatus._loadedBlockTypes.add(blockType); // We want the browser to handle this import(), not webpack, so the comment on the next line is essential. - import(/* webpackIgnore: true */ `${getConfig().LMS_BASE_URL}/xblock/resource-v2/${blockType}/public/learner-view-v2.js`).then(() => { + import(/* webpackIgnore: true */ `${getConfig().LMS_BASE_URL}/xblock/resource/${blockType}/public/learner-view-v2.js`).then(() => { console.log(`Loaded JavaScript for ${blockType} v2 XBlock.`); }, (err) => { console.error(`🛑 Unable to Load JavaScript for ${blockType} v2 XBlock: ${err}`); @@ -36,11 +31,20 @@ export function ensureBlockScript(blockType: string): void { } } -const callHandler: CallHandler = async (usageKey, handlerName, body, method): Promise => { +const callHandlerRaw: CallHandlerRaw = async (usageKey, handlerName, body, method = 'post'): Promise => { const client: AxiosInstance = getAuthenticatedHttpClient(); - const makeRequest = client[method === "POST" ? 'post' : 'get']; - const response = await makeRequest(`${getConfig().LMS_BASE_URL}/api/xblock/v1/${usageKey}/handler_v2/${handlerName}`, body); - return response.data; + const makeRequest = client[method]; + const axiosResponse = await makeRequest( + `${getConfig().LMS_BASE_URL}/xblock/${usageKey}/handler/${handlerName}`, + body, + { transformResponse: (r) => r } + ); + // Convert from the vague axios format to the standard Response format: + const headers: Record = ( + axiosResponse.headers instanceof AxiosHeaders ? axiosResponse.headers.toJSON(true) : axiosResponse.headers + ); + const response = new Response(axiosResponse.data, {headers, status: axiosResponse.status}); + return response; } /** @@ -53,7 +57,7 @@ export const XBlockRenderContextProvider: React.FC<{children: React.ReactNode}> const ref = React.useRef(null); React.useEffect(() => { if (ref.current) { - (ref.current as any).callHandler = callHandler; + (ref.current as any).callHandlerRaw = callHandlerRaw; } }, [ref.current]); return ( diff --git a/src/courseware/component/types.ts b/src/courseware/component/types.ts index e77b42d6ff..e6b4d81335 100644 --- a/src/courseware/component/types.ts +++ b/src/courseware/component/types.ts @@ -10,7 +10,6 @@ export interface XBlockDataV2 { id: string; blockType: string; contentFields: { displayName: string } & Record; - systemFields: Record; userFields: Record; }