pyret-embed 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/pyret.js CHANGED
@@ -14,7 +14,7 @@
14
14
  \**********************/
15
15
  (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
16
16
 
17
- eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ makeEmbed: () => (/* binding */ makeEmbed),\n/* harmony export */ makeEmbedConfig: () => (/* binding */ makeEmbedConfig)\n/* harmony export */ });\nconst CPO = \"https://pyret-horizon.herokuapp.com/editor\";\nconst defaultOptions = {\n footerStyle: 'hide',\n warnOnExit: false,\n hideDefinitions: false,\n hideInteractions: false\n};\nconst defaultConfig = {\n src: CPO,\n state: false,\n options: defaultOptions\n};\nfunction sendRpcResponse(frame, data, result) {\n frame.contentWindow.postMessage({\n protocol: 'pyret-rpc',\n data: {\n type: 'rpc-response',\n callbackId: data.callbackId,\n ...result\n }\n });\n}\nasync function receiveRPC(frame, e, rpcs) {\n console.log(\"RPC:\", e.data);\n const data = e.data.data;\n const module = rpcs[data.module];\n if (!module || !module[data.method]) {\n sendRpcResponse(frame, data, { resultType: 'exception', exception: `Unknown method ${data.method}` });\n }\n else {\n try {\n const result = await module[data.method](...data.args);\n sendRpcResponse(frame, data, { resultType: 'value', result });\n }\n catch (exn) {\n sendRpcResponse(frame, data, { resultType: 'exception', exception: String(exn) });\n }\n return;\n }\n}\nfunction makeEmbedConfig(config) {\n let mergedConfig = { ...defaultConfig, ...config };\n let mergedOptions = { ...defaultConfig.options, ...config.options };\n let { container, src } = mergedConfig;\n let id = config.id || (\"pyret-embed\" + Math.floor(Math.random() * 1000000));\n const hasprop = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);\n console.log(\"Pyret embed config:\", mergedConfig, mergedOptions);\n const propIfTrue = (obj, prop) => { if (obj[prop] === true) {\n return `&${prop}=true`;\n }\n else {\n return \"\";\n } };\n const propIfPresent = (obj, prop) => { if (hasprop(obj, prop)) {\n return `&${prop}=${obj[prop]}`;\n }\n else {\n return \"\";\n } };\n const fragment = `${propIfPresent(mergedOptions, \"footerStyle\")}${propIfPresent(mergedOptions, \"warnOnExit\")}${propIfTrue(mergedOptions, \"hideDefinitions\")}${propIfTrue(mergedOptions, \"hideInteractions\")}`;\n if (src.indexOf(\"#\") !== -1) {\n src = src + \"&\" + fragment;\n }\n else {\n src = src + \"#\" + fragment;\n }\n let messageNumber = 0;\n let currentState;\n function sendReset(frame, state) {\n if (!state) {\n state = {\n definitionsAtLastRun: false,\n interactionsSinceLastRun: [],\n editorContents: \"use context starter2024\",\n replContents: \"\"\n };\n }\n if (typeof state === \"object\") {\n state.messageNumber = 0;\n }\n currentState = state;\n const payload = {\n data: {\n type: 'reset',\n state: typeof state === \"string\" ? state : JSON.stringify(state)\n },\n protocol: 'pyret'\n };\n frame.contentWindow.postMessage(payload, '*');\n }\n function gainControl(frame) {\n frame.contentWindow.postMessage({\n type: 'gainControl'\n }, '*');\n }\n function setInteractions(frame, text) {\n messageNumber += 1;\n const change = {\n from: { line: 0, ch: 0 },\n to: { line: 0, ch: 0 },\n text: text\n };\n currentState = { ...currentState, messageNumber, replContents: text };\n const payload = {\n protocol: 'pyret',\n data: {\n type: 'changeRepl',\n change: change\n },\n state: currentState\n };\n frame.contentWindow.postMessage(payload, '*');\n }\n function runDefinitions(frame) {\n messageNumber += 1;\n currentState = { ...currentState, messageNumber, interactionsSinceLastRun: [], definitionsAtLastRun: currentState.editorContents };\n const payload = {\n protocol: 'pyret',\n data: {\n type: 'run'\n },\n state: currentState\n };\n frame.contentWindow.postMessage(payload, '*');\n }\n function clearInteractions(frame) {\n messageNumber += 1;\n const payload = {\n protocol: 'pyret',\n data: {\n type: 'clearInteractions'\n },\n state: currentState\n };\n frame.contentWindow.postMessage(payload, '*');\n }\n let resultCounter = 0;\n function runInteractionResult(frame) {\n const { promise, resolve, reject } = Promise.withResolvers();\n messageNumber += 1;\n const newInteractions = currentState.interactionsSinceLastRun.concat([currentState.replContents]);\n currentState = {\n ...currentState,\n messageNumber: messageNumber,\n interactionsSinceLastRun: newInteractions,\n replContents: \"\",\n };\n const payload = {\n protocol: 'pyret',\n data: {\n type: 'runInteraction',\n reportAnswer: 'interaction' + (++resultCounter)\n },\n state: currentState\n };\n frame.contentWindow.postMessage(payload, '*');\n window.addEventListener('message', message => {\n if (message.data.protocol !== 'pyret') {\n return;\n }\n if (message.source !== frame.contentWindow) {\n return;\n }\n const pyretMessage = message.data;\n if (pyretMessage.data.type === 'interactionResult') {\n resolve(pyretMessage.data.textResult);\n }\n });\n return promise;\n }\n function directPostMessage(frame, message) {\n frame.contentWindow.postMessage(message);\n }\n /* An issue we run into with iframes and scrolling is that CPO wants to scroll\n interactions around sometimes. However, scrolling elements in the iframe\n can scroll the outer page as well to focus it. We don't want that.\n We can prevent this by making the iframe position: fixed, but that makes\n sensible positioning hard. So we create a wrapper iframe that works in the\n normal flow, and make the actual CPO iframe inside that.\n \n Since CPO only knows how to postMessage to its immediate parent, we also\n proxy all requests through the wrapper, and that's what the client sees.\n */\n const wrapper = document.createElement(\"iframe\");\n wrapper.style = \"width: 100%; height: 100%; border: 0; display: block;\";\n wrapper.srcdoc = `\n<html>\n<head>\n<style>\nhtml, body { height: 100%; }\nbody { margin: 0; padding: 0; }\n</style>\n<script>\nwindow.addEventListener('message', (e) => {\n if (e.source === window.parent) {\n const iframes = document.getElementsByTagName(\"iframe\");\n iframes[0].contentWindow.postMessage(e.data, \"*\");\n }\n else {\n window.parent.postMessage(e.data, \"*\");\n }\n});\n</script>\n<body></body>\n</html>`;\n container.appendChild(wrapper);\n wrapper.addEventListener(\"load\", () => {\n const wrapperBody = wrapper.contentDocument.body;\n const inner = document.createElement(\"iframe\");\n inner.src = src || CPO;\n inner.style = \"width: 100%; height: 100%; border: 0; display: block; position: fixed;\";\n inner.width = \"100%\";\n inner.id = id;\n inner.frameBorder = \"0\";\n wrapperBody.appendChild(inner);\n });\n const frame = wrapper;\n frame.id = id;\n const { promise, resolve, reject } = Promise.withResolvers();\n setTimeout(() => reject(new Error(\"Timeout waiting for Pyret to load\")), 60000);\n const onChangeCallbacks = [];\n window.addEventListener('message', message => {\n if (message.data.protocol === 'pyret-rpc') {\n receiveRPC(frame, message, mergedConfig.rpc || {}).catch(exn => {\n console.error(\"Error in RPC handler:\", exn);\n });\n return;\n }\n if (message.data.protocol !== 'pyret') {\n return;\n }\n if (message.source !== frame.contentWindow) {\n return;\n }\n const pyretMessage = message.data;\n const typ = pyretMessage.data.type;\n if (typ === 'pyret-init') {\n gainControl(frame);\n if (mergedConfig.state) {\n sendReset(frame, mergedConfig.state);\n }\n const api = makeEmbedAPI(frame);\n frame.pyretEmbed = api;\n resolve(api);\n }\n else if (typ === \"changeRepl\" || typ === \"change\") {\n onChangeCallbacks.forEach(cb => cb(pyretMessage));\n currentState = pyretMessage.state;\n }\n else {\n currentState = pyretMessage.state;\n }\n });\n function makeEmbedAPI(frame) {\n return {\n sendReset: (state) => sendReset(frame, state),\n postMessage: (message) => directPostMessage(frame, message),\n getFrame: () => frame,\n setInteractions: (text) => setInteractions(frame, text),\n runDefinitions: () => runDefinitions(frame),\n runInteractionResult: async () => await runInteractionResult(frame),\n onChange: (callback) => onChangeCallbacks.push(callback),\n clearInteractions: () => clearInteractions(frame),\n currentState: () => currentState,\n };\n }\n return promise;\n}\nfunction makeEmbed(id, container, src) {\n const config = {\n container,\n id,\n options: {}\n };\n if (src) {\n config.src = src;\n }\n return makeEmbedConfig(config);\n}\n\n\n//# sourceURL=webpack://pyret-embed/./src/pyret.ts?\n}");
17
+ eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ makeEmbed: () => (/* binding */ makeEmbed),\n/* harmony export */ makeEmbedConfig: () => (/* binding */ makeEmbedConfig)\n/* harmony export */ });\nconst CPO = \"https://pyret-horizon.herokuapp.com/editor\";\nconst defaultOptions = {\n footerStyle: 'hide',\n warnOnExit: false,\n hideDefinitions: false,\n hideInteractions: false\n};\nconst defaultConfig = {\n src: CPO,\n state: false,\n options: defaultOptions\n};\nfunction sendRpcResponse(frame, data, result) {\n frame.contentWindow.postMessage({\n protocol: 'pyret-rpc',\n data: {\n type: 'rpc-response',\n callbackId: data.callbackId,\n ...result\n }\n });\n}\nasync function receiveRPC(frame, e, rpcs) {\n console.log(\"RPC:\", e.data);\n const data = e.data.data;\n const module = rpcs[data.module];\n if (!module || !module[data.method]) {\n sendRpcResponse(frame, data, { resultType: 'exception', exception: `Unknown method ${data.method}` });\n }\n else {\n try {\n const result = await module[data.method](...data.args);\n sendRpcResponse(frame, data, { resultType: 'value', result });\n }\n catch (exn) {\n sendRpcResponse(frame, data, { resultType: 'exception', exception: String(exn) });\n }\n return;\n }\n}\nfunction makeEmbedConfig(config) {\n let mergedConfig = { ...defaultConfig, ...config };\n let mergedOptions = { ...defaultConfig.options, ...config.options };\n let { container, src } = mergedConfig;\n let id = config.id || (\"pyret-embed\" + Math.floor(Math.random() * 1000000));\n const hasprop = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);\n console.log(\"Pyret embed config:\", mergedConfig, mergedOptions);\n const propIfTrue = (obj, prop) => { if (obj[prop] === true) {\n return `&${prop}=true`;\n }\n else {\n return \"\";\n } };\n const propIfPresent = (obj, prop) => { if (hasprop(obj, prop)) {\n return `&${prop}=${obj[prop]}`;\n }\n else {\n return \"\";\n } };\n const fragment = `${propIfPresent(mergedOptions, \"footerStyle\")}${propIfPresent(mergedOptions, \"warnOnExit\")}${propIfTrue(mergedOptions, \"hideDefinitions\")}${propIfTrue(mergedOptions, \"hideInteractions\")}`;\n if (mergedConfig.compiler && mergedConfig.compiler !== \"pyret\") {\n // The compiler choice travels as a query parameter (?compiler=ts), not a\n // hash option, mirroring code.pyret.org's /editor flag; insert it before\n // any hash fragment already present on src.\n const hashAt = src.indexOf(\"#\");\n const base = hashAt === -1 ? src : src.substring(0, hashAt);\n const hash = hashAt === -1 ? \"\" : src.substring(hashAt);\n src = base + (base.indexOf(\"?\") === -1 ? \"?\" : \"&\") + \"compiler=\" + mergedConfig.compiler + hash;\n }\n if (src.indexOf(\"#\") !== -1) {\n src = src + \"&\" + fragment;\n }\n else {\n src = src + \"#\" + fragment;\n }\n let messageNumber = 0;\n let currentState;\n function sendReset(frame, state) {\n if (!state) {\n state = {\n definitionsAtLastRun: false,\n interactionsSinceLastRun: [],\n editorContents: \"use context starter2024\",\n replContents: \"\"\n };\n }\n if (typeof state === \"object\") {\n state.messageNumber = 0;\n }\n currentState = state;\n const payload = {\n data: {\n type: 'reset',\n state: typeof state === \"string\" ? state : JSON.stringify(state)\n },\n protocol: 'pyret'\n };\n frame.contentWindow.postMessage(payload, '*');\n }\n function gainControl(frame) {\n frame.contentWindow.postMessage({\n type: 'gainControl'\n }, '*');\n }\n function setInteractions(frame, text) {\n messageNumber += 1;\n const change = {\n from: { line: 0, ch: 0 },\n to: { line: 0, ch: 0 },\n text: text\n };\n currentState = { ...currentState, messageNumber, replContents: text };\n const payload = {\n protocol: 'pyret',\n data: {\n type: 'changeRepl',\n change: change\n },\n state: currentState\n };\n frame.contentWindow.postMessage(payload, '*');\n }\n function runDefinitions(frame) {\n messageNumber += 1;\n currentState = { ...currentState, messageNumber, interactionsSinceLastRun: [], definitionsAtLastRun: currentState.editorContents };\n const payload = {\n protocol: 'pyret',\n data: {\n type: 'run'\n },\n state: currentState\n };\n frame.contentWindow.postMessage(payload, '*');\n }\n function clearInteractions(frame) {\n messageNumber += 1;\n const payload = {\n protocol: 'pyret',\n data: {\n type: 'clearInteractions'\n },\n state: currentState\n };\n frame.contentWindow.postMessage(payload, '*');\n }\n let resultCounter = 0;\n function runInteractionResult(frame) {\n const { promise, resolve, reject } = Promise.withResolvers();\n messageNumber += 1;\n const newInteractions = currentState.interactionsSinceLastRun.concat([currentState.replContents]);\n currentState = {\n ...currentState,\n messageNumber: messageNumber,\n interactionsSinceLastRun: newInteractions,\n replContents: \"\",\n };\n const payload = {\n protocol: 'pyret',\n data: {\n type: 'runInteraction',\n reportAnswer: 'interaction' + (++resultCounter)\n },\n state: currentState\n };\n frame.contentWindow.postMessage(payload, '*');\n window.addEventListener('message', message => {\n if (message.data.protocol !== 'pyret') {\n return;\n }\n if (message.source !== frame.contentWindow) {\n return;\n }\n const pyretMessage = message.data;\n if (pyretMessage.data.type === 'interactionResult') {\n resolve(pyretMessage.data.textResult);\n }\n });\n return promise;\n }\n function directPostMessage(frame, message) {\n frame.contentWindow.postMessage(message);\n }\n /* An issue we run into with iframes and scrolling is that CPO wants to scroll\n interactions around sometimes. However, scrolling elements in the iframe\n can scroll the outer page as well to focus it. We don't want that.\n We can prevent this by making the iframe position: fixed, but that makes\n sensible positioning hard. So we create a wrapper iframe that works in the\n normal flow, and make the actual CPO iframe inside that.\n \n Since CPO only knows how to postMessage to its immediate parent, we also\n proxy all requests through the wrapper, and that's what the client sees.\n */\n const wrapper = document.createElement(\"iframe\");\n wrapper.style = \"width: 100%; height: 100%; border: 0; display: block;\";\n wrapper.srcdoc = `\n<html>\n<head>\n<style>\nhtml, body { height: 100%; }\nbody { margin: 0; padding: 0; }\n</style>\n<script>\nwindow.addEventListener('message', (e) => {\n if (e.source === window.parent) {\n const iframes = document.getElementsByTagName(\"iframe\");\n iframes[0].contentWindow.postMessage(e.data, \"*\");\n }\n else {\n window.parent.postMessage(e.data, \"*\");\n }\n});\n</script>\n<body></body>\n</html>`;\n container.appendChild(wrapper);\n wrapper.addEventListener(\"load\", () => {\n const wrapperBody = wrapper.contentDocument.body;\n const inner = document.createElement(\"iframe\");\n inner.src = src || CPO;\n inner.style = \"width: 100%; height: 100%; border: 0; display: block; position: fixed;\";\n inner.width = \"100%\";\n inner.id = id;\n inner.frameBorder = \"0\";\n wrapperBody.appendChild(inner);\n });\n const frame = wrapper;\n frame.id = id;\n const { promise, resolve, reject } = Promise.withResolvers();\n setTimeout(() => reject(new Error(\"Timeout waiting for Pyret to load\")), 60000);\n const onChangeCallbacks = [];\n window.addEventListener('message', message => {\n if (message.data.protocol === 'pyret-rpc') {\n receiveRPC(frame, message, mergedConfig.rpc || {}).catch(exn => {\n console.error(\"Error in RPC handler:\", exn);\n });\n return;\n }\n if (message.data.protocol !== 'pyret') {\n return;\n }\n if (message.source !== frame.contentWindow) {\n return;\n }\n const pyretMessage = message.data;\n const typ = pyretMessage.data.type;\n if (typ === 'pyret-init') {\n gainControl(frame);\n if (mergedConfig.state) {\n sendReset(frame, mergedConfig.state);\n }\n const api = makeEmbedAPI(frame);\n frame.pyretEmbed = api;\n resolve(api);\n }\n else if (typ === \"changeRepl\" || typ === \"change\") {\n onChangeCallbacks.forEach(cb => cb(pyretMessage));\n currentState = pyretMessage.state;\n }\n else {\n currentState = pyretMessage.state;\n }\n });\n function makeEmbedAPI(frame) {\n return {\n sendReset: (state) => sendReset(frame, state),\n postMessage: (message) => directPostMessage(frame, message),\n getFrame: () => frame,\n setInteractions: (text) => setInteractions(frame, text),\n runDefinitions: () => runDefinitions(frame),\n runInteractionResult: async () => await runInteractionResult(frame),\n onChange: (callback) => onChangeCallbacks.push(callback),\n clearInteractions: () => clearInteractions(frame),\n currentState: () => currentState,\n };\n }\n return promise;\n}\nfunction makeEmbed(id, container, src) {\n const config = {\n container,\n id,\n options: {}\n };\n if (src) {\n config.src = src;\n }\n return makeEmbedConfig(config);\n}\n\n\n//# sourceURL=webpack://pyret-embed/./src/pyret.ts?\n}");
18
18
 
19
19
  /***/ }
20
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pyret-embed",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "A library for embedding Pyret into webpages",
5
5
  "main": "dist/pyret.js",
6
6
  "files": [
@@ -10,6 +10,7 @@
10
10
  "scripts": {
11
11
  "test": "echo \"Error: no test specified\" && exit 1",
12
12
  "build": "tsc; cd node_modules/code.pyret.org; make link-pyret; npm run build; cd ../..; npm run copy-pyret",
13
+ "build:ts": "npm run build; cd node_modules/code.pyret.org; make web-ts; cd ../..; npm run copy-pyret",
13
14
  "copy-pyret": "cp -r node_modules/code.pyret.org/build dist/"
14
15
  },
15
16
  "author": "",
package/types/pyret.d.ts CHANGED
@@ -50,6 +50,7 @@ export type EmbedConfig = {
50
50
  id?: string;
51
51
  state?: State;
52
52
  rpc?: RPCFunctions;
53
+ compiler?: 'pyret' | 'ts';
53
54
  options: {
54
55
  footerStyle?: 'hide' | 'normal';
55
56
  warnOnExit?: boolean;