gwchq-textjam 0.4.0 → 0.4.1

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/index.js CHANGED
@@ -64668,6 +64668,35 @@ function SvgGwcLogo(props) {
64668
64668
 
64669
64669
  /***/ }),
64670
64670
 
64671
+ /***/ 50424:
64672
+ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
64673
+
64674
+ __webpack_require__.r(__webpack_exports__);
64675
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
64676
+ /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
64677
+ /* harmony export */ });
64678
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(51649);
64679
+ var _path;
64680
+ function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
64681
+
64682
+ function SvgActivity(props) {
64683
+ return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement("svg", _extends({
64684
+ width: 24,
64685
+ height: 24,
64686
+ fill: "none",
64687
+ xmlns: "http://www.w3.org/2000/svg"
64688
+ }, props), _path || (_path = /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement("path", {
64689
+ d: "M12 7a4 4 0 014-4h5a1 1 0 011 1v13a1 1 0 01-1 1h-6a3 3 0 00-3 3m0-14v14m0-14a4 4 0 00-4-4H3a1 1 0 00-1 1v13a1 1 0 001 1h6a3 3 0 013 3m0-14v14",
64690
+ stroke: "#33625E",
64691
+ strokeWidth: 1.5,
64692
+ strokeLinecap: "round",
64693
+ strokeLinejoin: "round"
64694
+ })));
64695
+ }
64696
+ /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (SvgActivity);
64697
+
64698
+ /***/ }),
64699
+
64671
64700
  /***/ 41625:
64672
64701
  /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
64673
64702
 
@@ -64833,6 +64862,35 @@ function SvgButtonDots(props) {
64833
64862
 
64834
64863
  /***/ }),
64835
64864
 
64865
+ /***/ 62284:
64866
+ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
64867
+
64868
+ __webpack_require__.r(__webpack_exports__);
64869
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
64870
+ /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
64871
+ /* harmony export */ });
64872
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(51649);
64873
+ var _path;
64874
+ function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
64875
+
64876
+ function SvgCheckbox(props) {
64877
+ return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement("svg", _extends({
64878
+ width: 8,
64879
+ height: 6,
64880
+ fill: "none",
64881
+ xmlns: "http://www.w3.org/2000/svg"
64882
+ }, props), _path || (_path = /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement("path", {
64883
+ d: "M.75 2.75l2 2 4-4",
64884
+ stroke: "#33625E",
64885
+ strokeWidth: 1.5,
64886
+ strokeLinecap: "round",
64887
+ strokeLinejoin: "round"
64888
+ })));
64889
+ }
64890
+ /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (SvgCheckbox);
64891
+
64892
+ /***/ }),
64893
+
64836
64894
  /***/ 16882:
64837
64895
  /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
64838
64896
 
@@ -65494,6 +65552,35 @@ function SvgMinusCircle(props) {
65494
65552
 
65495
65553
  /***/ }),
65496
65554
 
65555
+ /***/ 36311:
65556
+ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
65557
+
65558
+ __webpack_require__.r(__webpack_exports__);
65559
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
65560
+ /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
65561
+ /* harmony export */ });
65562
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(51649);
65563
+ var _path;
65564
+ function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
65565
+
65566
+ function SvgMonitor(props) {
65567
+ return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement("svg", _extends({
65568
+ width: 32,
65569
+ height: 32,
65570
+ fill: "none",
65571
+ xmlns: "http://www.w3.org/2000/svg"
65572
+ }, props), _path || (_path = /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement("path", {
65573
+ d: "M10.8 28h10.4M16 22.667V28M5.6 4h20.8C27.836 4 29 5.194 29 6.667V20c0 1.473-1.164 2.667-2.6 2.667H5.6C4.164 22.667 3 21.473 3 20V6.667C3 5.194 4.164 4 5.6 4z",
65574
+ stroke: "#003046",
65575
+ strokeWidth: 2,
65576
+ strokeLinecap: "round",
65577
+ strokeLinejoin: "round"
65578
+ })));
65579
+ }
65580
+ /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (SvgMonitor);
65581
+
65582
+ /***/ }),
65583
+
65497
65584
  /***/ 97512:
65498
65585
  /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
65499
65586
 
@@ -68348,8 +68435,8 @@ __webpack_require__.r(__webpack_exports__);
68348
68435
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
68349
68436
  /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
68350
68437
  /* harmony export */ });
68351
- /* harmony import */ var D_gwc_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(89379);
68352
- /* harmony import */ var D_gwc_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectWithoutProperties_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(53986);
68438
+ /* harmony import */ var C_Project_source_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(89379);
68439
+ /* harmony import */ var C_Project_source_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectWithoutProperties_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(53986);
68353
68440
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(51649);
68354
68441
  /* harmony import */ var _hello_pangea_dnd__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(98850);
68355
68442
  /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(14062);
@@ -68373,7 +68460,7 @@ var DraggableTab = _ref => {
68373
68460
  panelIndex,
68374
68461
  fileIndex
68375
68462
  } = _ref,
68376
- otherProps = (0,D_gwc_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectWithoutProperties_js__WEBPACK_IMPORTED_MODULE_5__/* ["default"] */ .A)(_ref, _excluded);
68463
+ otherProps = (0,C_Project_source_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectWithoutProperties_js__WEBPACK_IMPORTED_MODULE_5__/* ["default"] */ .A)(_ref, _excluded);
68377
68464
  var openFiles = (0,react_redux__WEBPACK_IMPORTED_MODULE_1__.useSelector)(state => state.editor.openedFiles);
68378
68465
  var openFilesCount = openFiles[panelIndex].length;
68379
68466
  var dispatch = (0,react_redux__WEBPACK_IMPORTED_MODULE_1__.useDispatch)();
@@ -68390,7 +68477,7 @@ var DraggableTab = _ref => {
68390
68477
  switchToFileTab(panelIndex, (fileIndex + openFilesCount - 1) % openFilesCount);
68391
68478
  }
68392
68479
  };
68393
- var InnerTab = () => /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_4__.jsx)(react_tabs__WEBPACK_IMPORTED_MODULE_2__.Tab, (0,D_gwc_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A)((0,D_gwc_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A)({
68480
+ var InnerTab = () => /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_4__.jsx)(react_tabs__WEBPACK_IMPORTED_MODULE_2__.Tab, (0,C_Project_source_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A)((0,C_Project_source_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A)({
68394
68481
  onClick: e => {
68395
68482
  e.stopPropagation();
68396
68483
  switchToFileTab(panelIndex, fileIndex);
@@ -68408,7 +68495,7 @@ var DraggableTab = _ref => {
68408
68495
  draggableProps,
68409
68496
  dragHandleProps
68410
68497
  } = _ref2;
68411
- return /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_4__.jsx)("div", (0,D_gwc_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A)((0,D_gwc_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A)((0,D_gwc_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A)({
68498
+ return /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_4__.jsx)("div", (0,C_Project_source_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A)((0,C_Project_source_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A)((0,C_Project_source_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A)({
68412
68499
  className: "draggable-tab",
68413
68500
  ref: innerRef
68414
68501
  }, draggableProps), dragHandleProps), {}, {
@@ -68430,8 +68517,8 @@ __webpack_require__.r(__webpack_exports__);
68430
68517
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
68431
68518
  /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
68432
68519
  /* harmony export */ });
68433
- /* harmony import */ var D_gwc_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(89379);
68434
- /* harmony import */ var D_gwc_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectWithoutProperties_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(53986);
68520
+ /* harmony import */ var C_Project_source_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(89379);
68521
+ /* harmony import */ var C_Project_source_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectWithoutProperties_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(53986);
68435
68522
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(51649);
68436
68523
  /* harmony import */ var _hello_pangea_dnd__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(98850);
68437
68524
  /* harmony import */ var react_tabs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(39243);
@@ -68450,8 +68537,8 @@ var DroppableTabList = _ref => {
68450
68537
  children: _children,
68451
68538
  index
68452
68539
  } = _ref,
68453
- otherProps = (0,D_gwc_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectWithoutProperties_js__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .A)(_ref, _excluded);
68454
- return /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_3__.jsx)(react_tabs__WEBPACK_IMPORTED_MODULE_1__.TabList, (0,D_gwc_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_5__/* ["default"] */ .A)((0,D_gwc_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_5__/* ["default"] */ .A)({}, otherProps), {}, {
68540
+ otherProps = (0,C_Project_source_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectWithoutProperties_js__WEBPACK_IMPORTED_MODULE_4__/* ["default"] */ .A)(_ref, _excluded);
68541
+ return /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_3__.jsx)(react_tabs__WEBPACK_IMPORTED_MODULE_1__.TabList, (0,C_Project_source_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_5__/* ["default"] */ .A)((0,C_Project_source_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_5__/* ["default"] */ .A)({}, otherProps), {}, {
68455
68542
  children: /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_3__.jsx)(_hello_pangea_dnd__WEBPACK_IMPORTED_MODULE_6__.Droppable, {
68456
68543
  direction: "horizontal",
68457
68544
  droppableId: index.toString(),
@@ -68461,7 +68548,7 @@ var DroppableTabList = _ref => {
68461
68548
  droppableProps,
68462
68549
  placeholder
68463
68550
  } = _ref2;
68464
- return /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxs)("div", (0,D_gwc_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_5__/* ["default"] */ .A)((0,D_gwc_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_5__/* ["default"] */ .A)({
68551
+ return /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxs)("div", (0,C_Project_source_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_5__/* ["default"] */ .A)((0,C_Project_source_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_5__/* ["default"] */ .A)({
68465
68552
  className: "droppable-tab-list"
68466
68553
  }, droppableProps), {}, {
68467
68554
  ref: innerRef,
@@ -101805,8 +101892,10 @@ var editorLightTheme = EditorView.theme({
101805
101892
  var utils_settings = __webpack_require__(62161);
101806
101893
  // EXTERNAL MODULE: ./src/redux/stores/index.ts
101807
101894
  var stores = __webpack_require__(32132);
101808
- // EXTERNAL MODULE: ./src/components/AssetPreview/index.tsx
101809
- var AssetPreview = __webpack_require__(91078);
101895
+ // EXTERNAL MODULE: ./src/components/Editor/EditorStatistic/EditorStatistic.tsx
101896
+ var EditorStatistic = __webpack_require__(47272);
101897
+ // EXTERNAL MODULE: ./src/components/AssetPreviewPanel/index.tsx
101898
+ var AssetPreviewPanel = __webpack_require__(66360);
101810
101899
  ;// ./src/components/Editor/EditorPanel/EditorPanel.jsx
101811
101900
 
101812
101901
  /* eslint-disable react-hooks/exhaustive-deps */
@@ -101829,6 +101918,8 @@ var AssetPreview = __webpack_require__(91078);
101829
101918
 
101830
101919
 
101831
101920
 
101921
+
101922
+
101832
101923
 
101833
101924
 
101834
101925
 
@@ -101847,6 +101938,8 @@ var EditorPanel = _ref => {
101847
101938
  } = (0,es.useTranslation)();
101848
101939
  var settings = (0,external_react_.useContext)(utils_settings.SettingsContext);
101849
101940
  var [characterLimitExceeded, setCharacterLimitExceeded] = (0,external_react_.useState)(false);
101941
+ var [line, setLine] = (0,external_react_.useState)(0);
101942
+ var [position, setPosition] = (0,external_react_.useState)(0);
101850
101943
  var updateStoredProject = c(content => {
101851
101944
  dispatch((0,EditorSlice.updateProjectComponent)((0,objectSpread2/* default */.A)((0,objectSpread2/* default */.A)({}, file), {}, {
101852
101945
  content
@@ -101912,7 +102005,7 @@ var EditorPanel = _ref => {
101912
102005
  }, [file, cascadeUpdate, editorViewRef]);
101913
102006
  if (!file) return null;
101914
102007
  if (file.isBinary) {
101915
- return /*#__PURE__*/(0,jsx_runtime.jsx)(AssetPreview/* AssetPreview */.n, {
102008
+ return /*#__PURE__*/(0,jsx_runtime.jsx)(AssetPreviewPanel.AssetPreviewPanel, {
101916
102009
  file: file
101917
102010
  });
101918
102011
  }
@@ -101924,20 +102017,31 @@ var EditorPanel = _ref => {
101924
102017
  updateStoredProject(viewUpdate.state.doc.toString());
101925
102018
  }
101926
102019
  });
101927
- var getMode = () => {
101928
- switch (file.extension) {
101929
- case "html":
101930
- return html();
101931
- case "css":
101932
- return css();
101933
- case "py":
101934
- return python();
101935
- case "js":
101936
- return javascript();
101937
- default:
101938
- return html();
102020
+ var fileModes = {
102021
+ html: {
102022
+ title: "HTML",
102023
+ renderer: html()
102024
+ },
102025
+ css: {
102026
+ title: "CSS",
102027
+ renderer: css()
102028
+ },
102029
+ py: {
102030
+ title: "Python",
102031
+ renderer: python()
102032
+ },
102033
+ js: {
102034
+ title: "JavaScript",
102035
+ renderer: javascript()
101939
102036
  }
101940
102037
  };
102038
+ var getMode = () => {
102039
+ return !!fileModes[file.extension] ? fileModes[file.extension].renderer : fileModes["html"].renderer;
102040
+ };
102041
+ var getModeTitle = () => {
102042
+ return !!fileModes[file.extension] ? fileModes[file.extension].title : file.extension;
102043
+ };
102044
+
101941
102045
  // const isDarkMode =
101942
102046
  // cookies.theme === "dark" ||
101943
102047
  // (!cookies.theme &&
@@ -101946,11 +102050,24 @@ var EditorPanel = _ref => {
101946
102050
 
101947
102051
  // setting hardcode light theme for TextJam phase 1
101948
102052
  var editorTheme = editorLightTheme;
102053
+ var getCaretPosition = () => {
102054
+ var _editorViewRef$curren, _editorViewRef$curren2;
102055
+ var pos = (_editorViewRef$curren = editorViewRef.current) === null || _editorViewRef$curren === void 0 || (_editorViewRef$curren = _editorViewRef$curren.state) === null || _editorViewRef$curren === void 0 || (_editorViewRef$curren = _editorViewRef$curren.selection) === null || _editorViewRef$curren === void 0 || (_editorViewRef$curren = _editorViewRef$curren.main) === null || _editorViewRef$curren === void 0 ? void 0 : _editorViewRef$curren.head;
102056
+ var line = (_editorViewRef$curren2 = editorViewRef.current) === null || _editorViewRef$curren2 === void 0 ? void 0 : _editorViewRef$curren2.state.doc.lineAt(pos);
102057
+ setLine(line.number);
102058
+ setPosition(pos - line.from + 1);
102059
+ };
101949
102060
  return /*#__PURE__*/(0,jsx_runtime.jsxs)("div", {
101950
102061
  className: "editor-wrapper",
101951
102062
  children: [/*#__PURE__*/(0,jsx_runtime.jsx)("div", {
101952
102063
  className: "editor editor--".concat(settings.fontSize),
101953
- ref: editor
102064
+ ref: editor,
102065
+ onKeyUp: getCaretPosition,
102066
+ onClick: getCaretPosition
102067
+ }), /*#__PURE__*/(0,jsx_runtime.jsx)(EditorStatistic.EditorStatistic, {
102068
+ line: line,
102069
+ position: position,
102070
+ title: getModeTitle()
101954
102071
  }), characterLimitExceeded && /*#__PURE__*/(0,jsx_runtime.jsx)(er, {
101955
102072
  title: t("editorPanel.characterLimitError"),
101956
102073
  type: "error",
@@ -102034,7 +102151,8 @@ var Project = props => {
102034
102151
  var {
102035
102152
  nameEditable = true,
102036
102153
  sidebarOptions = [],
102037
- packageApiUrl
102154
+ packageApiUrl,
102155
+ curriculumComponent
102038
102156
  } = props;
102039
102157
  var isSharedProject = (0,external_react_redux_.useSelector)(state => state.editor.isSharedProject);
102040
102158
  var isCodeVisible = (0,external_react_redux_.useSelector)(state => state.editor.isCodeVisible);
@@ -102062,7 +102180,8 @@ var Project = props => {
102062
102180
  }), /*#__PURE__*/(0,jsx_runtime.jsxs)("div", {
102063
102181
  className: styles_module.projContainer,
102064
102182
  children: [/*#__PURE__*/(0,jsx_runtime.jsx)((Sidebar_default()), {
102065
- options: sidebarOptions
102183
+ options: sidebarOptions,
102184
+ curriculumComponent: curriculumComponent
102066
102185
  }), /*#__PURE__*/(0,jsx_runtime.jsx)("div", {
102067
102186
  className: styles_module.projectWrapper,
102068
102187
  ref: containerRef,
@@ -102158,8 +102277,10 @@ var SidebarPanel = props => {
102158
102277
  heading,
102159
102278
  Footer,
102160
102279
  className,
102280
+ panelContentClassName,
102161
102281
  buttons = [],
102162
- defaultWidth = "270px"
102282
+ defaultWidth = "270px",
102283
+ maxWidth = "600px"
102163
102284
  } = props;
102164
102285
  var isMobile = (0,react_responsive__WEBPACK_IMPORTED_MODULE_4__.useMediaQuery)({
102165
102286
  query: _utils_mediaQueryBreakpoints__WEBPACK_IMPORTED_MODULE_5__.MOBILE_MEDIA_QUERY
@@ -102175,7 +102296,7 @@ var SidebarPanel = props => {
102175
102296
  children: buttons
102176
102297
  })]
102177
102298
  }), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_3__.jsx)("div", {
102178
- className: _styles_module_scss__WEBPACK_IMPORTED_MODULE_2__["default"].panelContent,
102299
+ className: classnames__WEBPACK_IMPORTED_MODULE_1___default()(_styles_module_scss__WEBPACK_IMPORTED_MODULE_2__["default"].panelContent, panelContentClassName),
102179
102300
  children: children
102180
102301
  }), Footer && /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_3__.jsx)("div", {
102181
102302
  className: _styles_module_scss__WEBPACK_IMPORTED_MODULE_2__["default"].panelFooter,
@@ -102193,7 +102314,7 @@ var SidebarPanel = props => {
102193
102314
  defaultHeight: "100%",
102194
102315
  handleDirection: "right",
102195
102316
  minWidth: "250px",
102196
- maxWidth: "600px",
102317
+ maxWidth: maxWidth,
102197
102318
  children: panelContent
102198
102319
  });
102199
102320
  };
@@ -103015,7 +103136,7 @@ __webpack_require__.r(__webpack_exports__);
103015
103136
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
103016
103137
  /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
103017
103138
  /* harmony export */ });
103018
- /* harmony import */ var D_gwc_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(89379);
103139
+ /* harmony import */ var C_Project_source_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(89379);
103019
103140
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(51649);
103020
103141
  /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(14062);
103021
103142
  /* harmony import */ var _redux_EditorSlice__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(68512);
@@ -103129,7 +103250,7 @@ var ProjectName = _ref => {
103129
103250
  id: "project_name_label",
103130
103251
  className: _styles_module_scss__WEBPACK_IMPORTED_MODULE_3__["default"].projectLabel,
103131
103252
  children: "Project Name"
103132
- }), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxs)("div", (0,D_gwc_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A)((0,D_gwc_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A)({
103253
+ }), /*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxs)("div", (0,C_Project_source_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A)((0,C_Project_source_gwchq_textjam_node_modules_babel_runtime_helpers_esm_objectSpread2_js__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A)({
103133
103254
  className: classnames__WEBPACK_IMPORTED_MODULE_2___default()(_styles_module_scss__WEBPACK_IMPORTED_MODULE_3__["default"].projectName, className)
103134
103255
  }, hoverProps), {}, {
103135
103256
  children: [/*#__PURE__*/(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_4__.jsx)((components_Tooltip_Tooltip__WEBPACK_IMPORTED_MODULE_8___default()), {
@@ -103147,6 +103268,7 @@ var ProjectName = _ref => {
103147
103268
  onKeyUp: handleKeyUp,
103148
103269
  value: name,
103149
103270
  readOnly: !isEditing,
103271
+ maxLength: 255,
103150
103272
  onChange: handleOnChange,
103151
103273
  onBlur: handleOnBlur,
103152
103274
  className: classnames__WEBPACK_IMPORTED_MODULE_2___default()(_styles_module_scss__WEBPACK_IMPORTED_MODULE_3__["default"].projectInput, {
@@ -103213,6 +103335,11 @@ var Button = __webpack_require__(79428);
103213
103335
  var external_react_redux_ = __webpack_require__(14062);
103214
103336
  // EXTERNAL MODULE: ./src/redux/EditorSlice.ts
103215
103337
  var EditorSlice = __webpack_require__(68512);
103338
+ // EXTERNAL MODULE: ./src/components/Tooltip/Tooltip.tsx
103339
+ var Tooltip = __webpack_require__(26982);
103340
+ var Tooltip_default = /*#__PURE__*/__webpack_require__.n(Tooltip);
103341
+ // EXTERNAL MODULE: ./src/hooks/useHover.ts
103342
+ var useHover = __webpack_require__(78556);
103216
103343
  // EXTERNAL MODULE: ./node_modules/react/jsx-runtime.js
103217
103344
  var jsx_runtime = __webpack_require__(74848);
103218
103345
  ;// ./src/components/RunButton/RunButton.jsx
@@ -103224,6 +103351,8 @@ var _excluded = ["className"];
103224
103351
 
103225
103352
 
103226
103353
 
103354
+
103355
+
103227
103356
  var RunButton = _ref => {
103228
103357
  var {
103229
103358
  className
@@ -103233,15 +103362,26 @@ var RunButton = _ref => {
103233
103362
  var activeRunner = (0,external_react_redux_.useSelector)(state => state.editor.activeRunner);
103234
103363
  var loadedRunner = (0,external_react_redux_.useSelector)(state => state.editor.loadedRunner);
103235
103364
  var dispatch = (0,external_react_redux_.useDispatch)();
103365
+ var {
103366
+ hovered,
103367
+ hoverProps
103368
+ } = (0,useHover.useHover)();
103236
103369
  var onClickRun = () => {
103237
103370
  dispatch((0,EditorSlice.triggerCodeRun)());
103238
103371
  // reset page when running code to ensure it starts from the beginning
103239
103372
  dispatch((0,EditorSlice.setPage)(null));
103240
103373
  };
103241
- return /*#__PURE__*/(0,jsx_runtime.jsx)(Button["default"], (0,objectSpread2/* default */.A)({
103242
- disabled: !activeRunner || activeRunner !== loadedRunner || codeRunLoading,
103243
- onClickHandler: onClickRun
103244
- }, props));
103374
+ var isButtonDisabled = !activeRunner || activeRunner !== loadedRunner || codeRunLoading;
103375
+ return /*#__PURE__*/(0,jsx_runtime.jsxs)("div", (0,objectSpread2/* default */.A)((0,objectSpread2/* default */.A)({}, hoverProps), {}, {
103376
+ children: [/*#__PURE__*/(0,jsx_runtime.jsx)((Tooltip_default()), {
103377
+ message: "We\u2019re still loading what you need to run your code. Try again in a moment!",
103378
+ visible: hovered && isButtonDisabled,
103379
+ position: "bottom"
103380
+ }), /*#__PURE__*/(0,jsx_runtime.jsx)(Button["default"], (0,objectSpread2/* default */.A)({
103381
+ disabled: isButtonDisabled,
103382
+ onClickHandler: onClickRun
103383
+ }, props))]
103384
+ }));
103245
103385
  };
103246
103386
  /* harmony default export */ const RunButton_RunButton = (RunButton);
103247
103387
  // EXTERNAL MODULE: ./node_modules/classnames/index.js
@@ -103460,6 +103600,7 @@ var jsx_runtime = __webpack_require__(74848);
103460
103600
 
103461
103601
 
103462
103602
  var NO_CHANGES_TO_SAVE_MESSAGE = "No new changes to save!";
103603
+ var DEFAULT_TOOLTIP = "Save project";
103463
103604
  var SaveButton = props => {
103464
103605
  var dispatch = (0,external_react_redux_.useDispatch)();
103465
103606
  var {
@@ -103489,8 +103630,8 @@ var SaveButton = props => {
103489
103630
  className: styles_module.wrapper
103490
103631
  }, hoverProps), {}, {
103491
103632
  children: [/*#__PURE__*/(0,jsx_runtime.jsx)((Tooltip_default()), {
103492
- message: NO_CHANGES_TO_SAVE_MESSAGE,
103493
- visible: !hasChangesToSave && hovered,
103633
+ message: DEFAULT_TOOLTIP + (hasChangesToSave ? "" : "\n" + NO_CHANGES_TO_SAVE_MESSAGE),
103634
+ visible: hovered,
103494
103635
  position: "bottom"
103495
103636
  }), /*#__PURE__*/(0,jsx_runtime.jsx)(Button["default"], (0,objectSpread2/* default */.A)({
103496
103637
  buttonText: buttonText,
@@ -106198,6 +106339,14 @@ instance.use(es.initReactI18next)
106198
106339
 
106199
106340
  interpolation: {
106200
106341
  escapeValue: false // not needed for react!!
106342
+ },
106343
+ // Disable Suspense integration in react-i18next so that translation
106344
+ // loads (initial + i18n.changeLanguage) never throw promises that would
106345
+ // bubble to the root <Suspense fallback={<Loading />}> in App.tsx and
106346
+ // remount the Loading component (restarting the GIF / dot animation).
106347
+ // The visible loading state is driven by redux in WebComponentLoader.
106348
+ react: {
106349
+ useSuspense: false
106201
106350
  }
106202
106351
  });
106203
106352
  /* harmony default export */ const i18n = (instance);
@@ -142811,6 +142960,18 @@ __webpack_require__.r(__webpack_exports__);
142811
142960
  // extracted by mini-css-extract-plugin
142812
142961
 
142813
142962
 
142963
+ /***/ }),
142964
+
142965
+ /***/ 22401:
142966
+ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
142967
+
142968
+ __webpack_require__.r(__webpack_exports__);
142969
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
142970
+ /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
142971
+ /* harmony export */ });
142972
+ // extracted by mini-css-extract-plugin
142973
+ /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({"previewContainer":"styles-module__previewContainer--gHB-N"});
142974
+
142814
142975
  /***/ }),
142815
142976
 
142816
142977
  /***/ 63724:
@@ -142837,6 +142998,18 @@ __webpack_require__.r(__webpack_exports__);
142837
142998
 
142838
142999
  /***/ }),
142839
143000
 
143001
+ /***/ 89464:
143002
+ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
143003
+
143004
+ __webpack_require__.r(__webpack_exports__);
143005
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
143006
+ /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
143007
+ /* harmony export */ });
143008
+ // extracted by mini-css-extract-plugin
143009
+ /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({"grey-rpi-grey-15":"#d5d7dc","grey-rpi-grey-40":"#9497a4","grey-rpi-grey-5":"#f1f2f3","grey-rpi-grey-70":"#4a4d59","grey-rpf-white":"#fff","editorStatistic":"styles-module__editorStatistic--EyK0S","contentType":"styles-module__contentType--Sb4eE"});
143010
+
143011
+ /***/ }),
143012
+
142840
143013
  /***/ 71244:
142841
143014
  /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
142842
143015
 
@@ -142861,6 +143034,18 @@ __webpack_require__.r(__webpack_exports__);
142861
143034
 
142862
143035
  /***/ }),
142863
143036
 
143037
+ /***/ 38231:
143038
+ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
143039
+
143040
+ __webpack_require__.r(__webpack_exports__);
143041
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
143042
+ /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
143043
+ /* harmony export */ });
143044
+ // extracted by mini-css-extract-plugin
143045
+ /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({"grey-rpi-grey-15":"#d5d7dc","grey-rpi-grey-40":"#9497a4","grey-rpi-grey-5":"#f1f2f3","grey-rpi-grey-70":"#4a4d59","grey-rpf-white":"#fff","container":"styles-module__container--cs4+2","title":"styles-module__title--7NEra"});
143046
+
143047
+ /***/ }),
143048
+
142864
143049
  /***/ 12914:
142865
143050
  /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
142866
143051
 
@@ -142909,6 +143094,18 @@ __webpack_require__.r(__webpack_exports__);
142909
143094
 
142910
143095
  /***/ }),
142911
143096
 
143097
+ /***/ 99016:
143098
+ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
143099
+
143100
+ __webpack_require__.r(__webpack_exports__);
143101
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
143102
+ /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
143103
+ /* harmony export */ });
143104
+ // extracted by mini-css-extract-plugin
143105
+ /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({"curriculumPanel":"styles-module__curriculumPanel--UARP0","panelContent":"styles-module__panelContent--eZsVv"});
143106
+
143107
+ /***/ }),
143108
+
142912
143109
  /***/ 52160:
142913
143110
  /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
142914
143111
 
@@ -142941,7 +143138,7 @@ __webpack_require__.r(__webpack_exports__);
142941
143138
  /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
142942
143139
  /* harmony export */ });
142943
143140
  // extracted by mini-css-extract-plugin
142944
- /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({"grey-rpi-grey-15":"#d5d7dc","grey-rpi-grey-40":"#9497a4","grey-rpi-grey-5":"#f1f2f3","grey-rpi-grey-70":"#4a4d59","grey-rpf-white":"#fff","tree":"styles-module__tree--KziAJ","fileTreeActions":"styles-module__fileTreeActions--k+Wsd","fileTreeActionsLabel":"styles-module__fileTreeActionsLabel--dPZp4","importButton":"styles-module__importButton--rcLUm","importArrowIcon":"styles-module__importArrowIcon--3eOLN","label":"styles-module__label--WY51f","iconButton":"styles-module__iconButton--6UIVe","iconButtonFilled":"styles-module__iconButtonFilled--w2vJC","dragline":"styles-module__dragline--RXmTT","emptyStateContainer":"styles-module__emptyStateContainer--AnTAm","emptyStateIcon":"styles-module__emptyStateIcon--sDlxK","emptyStateMessage":"styles-module__emptyStateMessage--OkPax"});
143141
+ /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({"grey-rpi-grey-15":"#d5d7dc","grey-rpi-grey-40":"#9497a4","grey-rpi-grey-5":"#f1f2f3","grey-rpi-grey-70":"#4a4d59","grey-rpf-white":"#fff","tree":"styles-module__tree--KziAJ","fileTreeActions":"styles-module__fileTreeActions--k+Wsd","tooltipWrapper":"styles-module__tooltipWrapper--wltXE","fileTreeActionsLabel":"styles-module__fileTreeActionsLabel--dPZp4","importButton":"styles-module__importButton--rcLUm","importArrowIcon":"styles-module__importArrowIcon--3eOLN","label":"styles-module__label--WY51f","iconButton":"styles-module__iconButton--6UIVe","iconButtonFilled":"styles-module__iconButtonFilled--w2vJC","dragline":"styles-module__dragline--RXmTT","emptyStateContainer":"styles-module__emptyStateContainer--AnTAm","emptyStateIcon":"styles-module__emptyStateIcon--sDlxK","emptyStateMessage":"styles-module__emptyStateMessage--OkPax"});
142945
143142
 
142946
143143
  /***/ }),
142947
143144
 
@@ -368642,9 +368839,14 @@ const AuthContext_1 = __webpack_require__(55471);
368642
368839
  __webpack_require__(41527);
368643
368840
  const editorListener_1 = __webpack_require__(41824);
368644
368841
  const Loading_1 = __importDefault(__webpack_require__(34466));
368842
+ const types_1 = __webpack_require__(92932);
368645
368843
  const LeaveGuardController_1 = __webpack_require__(65240);
368646
368844
  (0, editorListener_1.registerEditorListeners)();
368647
- const TextJamEditor = ({ onLogoutClick = () => { }, onViewProfileClick = () => { }, onHomeClick = () => { }, saveProject, isSaving, projectContent = null, starterCodeContent = null, template, shareLinks = null, navigationGuardCoordinator, ...componentProps }) => {
368845
+ const DEFAULT_CURRICULUM = {
368846
+ curriculumContent: null,
368847
+ curriculumDisplayMode: types_1.CurriculumDisplayMode.HIDDEN,
368848
+ };
368849
+ const TextJamEditor = ({ onLogoutClick = () => { }, onViewProfileClick = () => { }, onHomeClick = () => { }, saveProject, isSaving, projectContent = null, starterCodeContent = null, template, shareLinks = null, navigationGuardCoordinator, curriculum = DEFAULT_CURRICULUM, ...componentProps }) => {
368648
368850
  // Default props that match the previous web-component defaults
368649
368851
  const defaultProps = {
368650
368852
  sidebarOptions: [
@@ -368668,6 +368870,7 @@ const TextJamEditor = ({ onLogoutClick = () => { }, onViewProfileClick = () => {
368668
368870
  starterCodeContent,
368669
368871
  template,
368670
368872
  shareLinks,
368873
+ curriculum,
368671
368874
  };
368672
368875
  const store = (0, react_1.useMemo)(() => (0, stores_1.createAppStore)(), []);
368673
368876
  (0, react_1.useEffect)(() => {
@@ -368685,15 +368888,34 @@ const TextJamEditor = ({ onLogoutClick = () => { }, onViewProfileClick = () => {
368685
368888
  exports.TextJamEditor = TextJamEditor;
368686
368889
 
368687
368890
 
368891
+ /***/ }),
368892
+
368893
+ /***/ 66360:
368894
+ /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
368895
+
368896
+
368897
+ var __importDefault = (this && this.__importDefault) || function (mod) {
368898
+ return (mod && mod.__esModule) ? mod : { "default": mod };
368899
+ };
368900
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
368901
+ exports.AssetPreviewPanel = void 0;
368902
+ const jsx_runtime_1 = __webpack_require__(74848);
368903
+ const styles_module_scss_1 = __importDefault(__webpack_require__(22401));
368904
+ const AssetPreview_1 = __webpack_require__(91078);
368905
+ const AssetPreviewPanel = ({ file, }) => {
368906
+ return ((0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.previewContainer, children: (0, jsx_runtime_1.jsx)(AssetPreview_1.AssetPreview, { file: file }) }));
368907
+ };
368908
+ exports.AssetPreviewPanel = AssetPreviewPanel;
368909
+
368910
+
368688
368911
  /***/ }),
368689
368912
 
368690
368913
  /***/ 91078:
368691
368914
  /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
368692
368915
 
368693
- var __webpack_unused_export__;
368694
368916
 
368695
- __webpack_unused_export__ = ({ value: true });
368696
- exports.n = void 0;
368917
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
368918
+ exports.AssetPreview = void 0;
368697
368919
  const jsx_runtime_1 = __webpack_require__(74848);
368698
368920
  const react_1 = __webpack_require__(51649);
368699
368921
  const binaryStore_1 = __webpack_require__(5060);
@@ -368720,7 +368942,7 @@ const AssetPreview = ({ file }) => {
368720
368942
  if (!url)
368721
368943
  return null;
368722
368944
  if (file.mimeType?.startsWith("image/")) {
368723
- return ((0, jsx_runtime_1.jsx)("img", { src: url, alt: file.name || "Asset preview", style: { maxWidth: "100%" } }));
368945
+ return ((0, jsx_runtime_1.jsx)("img", { src: url, alt: file.name || "Asset preview", style: { width: "fit-content", maxWidth: "100%" } }));
368724
368946
  }
368725
368947
  if (file.mimeType?.startsWith("audio/")) {
368726
368948
  return (0, jsx_runtime_1.jsx)("audio", { src: url, controls: true, style: { margin: 20 } });
@@ -368733,7 +368955,7 @@ const AssetPreview = ({ file }) => {
368733
368955
  }
368734
368956
  return (0, jsx_runtime_1.jsxs)("div", { children: ["File: ", file.name] });
368735
368957
  };
368736
- exports.n = AssetPreview;
368958
+ exports.AssetPreview = AssetPreview;
368737
368959
 
368738
368960
 
368739
368961
  /***/ }),
@@ -368805,7 +369027,7 @@ const HistoryButton = ({ className }) => {
368805
369027
  const handleClick = () => {
368806
369028
  setIsContextOpen((prev) => !prev);
368807
369029
  };
368808
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.btnContainer, ...hoverProps, children: [(0, jsx_runtime_1.jsx)(Tooltip_1.default, { message: "Nothing here yet! Once you save your work, versions will appear here so you can restore them.", visible: hovered && !commits?.length, position: "bottom" }), (0, jsx_runtime_1.jsx)(Button_1.default, { buttonRef: ref, className: (0, classnames_1.default)(styles_module_scss_1.default.historyButton, className, {
369030
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.btnContainer, ...hoverProps, children: [(0, jsx_runtime_1.jsx)(Tooltip_1.default, { message: "Nothing here yet! Once you save your work, versions will appear here so you can restore them.", visible: hovered && !commits?.length, position: "bottom" }), (0, jsx_runtime_1.jsx)(Tooltip_1.default, { message: "Load previous versions", visible: hovered && !!commits?.length, position: "bottom" }), (0, jsx_runtime_1.jsx)(Button_1.default, { buttonRef: ref, className: (0, classnames_1.default)(styles_module_scss_1.default.historyButton, className, {
368809
369031
  [styles_module_scss_1.default.historyActive]: isContextOpen,
368810
369032
  }), variant: "tertiary", onClickHandler: handleClick, ButtonIcon: () => (0, SvgIcon_1.SvgIcon)({ SvgElement: history_svg_1.default, size: 24 }), disabled: !commits?.length })] }), (0, jsx_runtime_1.jsx)(CommitHistoryMenu_1.CommitHistoryMenu, { isOpen: isContextOpen, onClose: () => setIsContextOpen(false), commits: commits, btnRef: ref, activeCommitId: project.commitId ?? undefined })] }));
368811
369033
  };
@@ -368852,13 +369074,16 @@ const stores_1 = __webpack_require__(32132);
368852
369074
  const SvgIcon_1 = __webpack_require__(82917);
368853
369075
  const Button_1 = __importDefault(__webpack_require__(79428));
368854
369076
  const createProjectArchive_1 = __webpack_require__(46514);
369077
+ const Tooltip_1 = __importDefault(__webpack_require__(26982));
369078
+ const useHover_1 = __webpack_require__(78556);
368855
369079
  const DownloadButton = (props) => {
368856
369080
  const project = (0, stores_1.useAppSelector)((state) => state.editor.project);
369081
+ const { hovered, hoverProps } = (0, useHover_1.useHover)();
368857
369082
  const onClickDownload = async () => {
368858
369083
  const { zipBlob: content } = await (0, createProjectArchive_1.createProjectArchive)(project);
368859
369084
  file_saver_1.default.saveAs(content, `${(0, js_convert_case_1.toSnakeCase)(project.name || "untitled_project")}.zip`);
368860
369085
  };
368861
- return ((0, jsx_runtime_1.jsx)(Button_1.default, { variant: "tertiary", ButtonIcon: () => (0, jsx_runtime_1.jsx)(SvgIcon_1.SvgIcon, { SvgElement: download_svg_1.default, size: 24 }), onClickHandler: onClickDownload, ...props }));
369086
+ return ((0, jsx_runtime_1.jsxs)("div", { ...hoverProps, children: [(0, jsx_runtime_1.jsx)(Button_1.default, { variant: "tertiary", ButtonIcon: () => (0, jsx_runtime_1.jsx)(SvgIcon_1.SvgIcon, { SvgElement: download_svg_1.default, size: 24 }), onClickHandler: onClickDownload, ...props }), (0, jsx_runtime_1.jsx)(Tooltip_1.default, { message: "Download project", visible: hovered, position: "bottom" })] }));
368862
369087
  };
368863
369088
  exports["default"] = DownloadButton;
368864
369089
 
@@ -369049,6 +369274,28 @@ const EditorInput = () => {
369049
369274
  exports["default"] = EditorInput;
369050
369275
 
369051
369276
 
369277
+ /***/ }),
369278
+
369279
+ /***/ 47272:
369280
+ /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
369281
+
369282
+
369283
+ var __importDefault = (this && this.__importDefault) || function (mod) {
369284
+ return (mod && mod.__esModule) ? mod : { "default": mod };
369285
+ };
369286
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
369287
+ exports.EditorStatistic = void 0;
369288
+ const jsx_runtime_1 = __webpack_require__(74848);
369289
+ const SvgIcon_1 = __webpack_require__(82917);
369290
+ const checkbox_svg_1 = __importDefault(__webpack_require__(62284));
369291
+ const styles_module_scss_1 = __importDefault(__webpack_require__(89464));
369292
+ const EditorStatistic = (props) => {
369293
+ const TAB_SIZE = 2;
369294
+ return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.editorStatistic, children: [(0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.contentType, children: ["{", (0, jsx_runtime_1.jsx)(SvgIcon_1.SvgIcon, { size: 6, SvgElement: checkbox_svg_1.default }), "} ", props.title] }), (0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.contentStatistic, children: ["Ln ", props.line, ", Col ", props.position, " * Spaces ", TAB_SIZE] })] }));
369295
+ };
369296
+ exports.EditorStatistic = EditorStatistic;
369297
+
369298
+
369052
369299
  /***/ }),
369053
369300
 
369054
369301
  /***/ 80335:
@@ -369197,7 +369444,6 @@ const node_html_parser_1 = __webpack_require__(36192);
369197
369444
  const classnames_1 = __importDefault(__webpack_require__(46942));
369198
369445
  const react_router_dom_1 = __webpack_require__(92648);
369199
369446
  const EditorSlice_1 = __webpack_require__(68512);
369200
- const externalLinkHelper_1 = __webpack_require__(31615);
369201
369447
  const open_in_new_tab_svg_1 = __importDefault(__webpack_require__(86936));
369202
369448
  const stores_1 = __webpack_require__(32132);
369203
369449
  const ProjectTypes_1 = __webpack_require__(27130);
@@ -369208,6 +369454,7 @@ const preview_svg_1 = __importDefault(__webpack_require__(80417));
369208
369454
  const OutputTabPanel_1 = __webpack_require__(25931);
369209
369455
  const styles_module_scss_1 = __importDefault(__webpack_require__(12914));
369210
369456
  const helpers_1 = __webpack_require__(1108);
369457
+ const projectPath_1 = __webpack_require__(95506);
369211
369458
  const fileParsers_1 = __webpack_require__(26683);
369212
369459
  const scripts_1 = __webpack_require__(57621);
369213
369460
  const Errors_1 = __webpack_require__(20339);
@@ -369223,6 +369470,8 @@ const getConsoleKey = (message) => JSON.stringify({
369223
369470
  method: message.method,
369224
369471
  data: message.data,
369225
369472
  });
369473
+ // A standalone preview view is flagged by `?preview=1` in the URL.
369474
+ const isPreviewSearch = (params) => params.get("preview") === "1";
369226
369475
  function HtmlRunner() {
369227
369476
  const [consoleLogs, setConsoleLogs] = (0, react_1.useState)([]);
369228
369477
  const project = (0, stores_1.useAppSelector)((state) => state.editor.project);
@@ -369235,9 +369484,19 @@ function HtmlRunner() {
369235
369484
  const isPreviewMode = (0, stores_1.useAppSelector)((state) => state.editor.isOutputOnly);
369236
369485
  const dispatch = (0, react_redux_1.useDispatch)();
369237
369486
  const output = (0, react_1.useRef)(null);
369238
- const [searchParams, setSearchParams] = (0, react_router_dom_1.useSearchParams)();
369487
+ const [searchParams] = (0, react_router_dom_1.useSearchParams)();
369239
369488
  // Using BroadcastChannel to communicate between the main app and the preview tab
369240
369489
  const broadcastChannel = (0, react_1.useRef)(null);
369490
+ const shouldUseBrowserHistoryRef = (0, react_1.useRef)(false);
369491
+ const currentPageParam = searchParams.get("page");
369492
+ const isPreviewUrl = isPreviewSearch(searchParams);
369493
+ // In the standalone preview tab (and any ?preview=1 URL) the browser's
369494
+ // back/forward/refresh buttons must drive page navigation.
369495
+ const shouldUseBrowserHistory = isPreviewMode || isPreviewUrl;
369496
+ const [iframeKey, setIframeKey] = (0, react_1.useState)(0);
369497
+ (0, react_1.useEffect)(() => {
369498
+ shouldUseBrowserHistoryRef.current = shouldUseBrowserHistory;
369499
+ }, [shouldUseBrowserHistory]);
369241
369500
  (0, react_1.useEffect)(() => {
369242
369501
  broadcastChannel.current = new BroadcastChannel(BROADCAST_CHANNEL);
369243
369502
  return () => {
@@ -369257,18 +369516,58 @@ function HtmlRunner() {
369257
369516
  defaultPreviewFilePath = page;
369258
369517
  }
369259
369518
  const [runningFilePath, setRunningFilePath] = (0, react_1.useState)(defaultPreviewFilePath);
369519
+ const writePreviewUrlToHistory = (path, replace = false) => {
369520
+ const normalizedPath = (0, projectPath_1.normalizePath)(path);
369521
+ const url = new URL(window.location.href);
369522
+ if (url.searchParams.get("page") === normalizedPath) {
369523
+ return false;
369524
+ }
369525
+ url.searchParams.set("preview", "1");
369526
+ url.searchParams.set("page", normalizedPath);
369527
+ if (replace) {
369528
+ window.history.replaceState({ __textJamPreview: true, page: normalizedPath }, "", url.toString());
369529
+ }
369530
+ else {
369531
+ window.history.pushState({ __textJamPreview: true, page: normalizedPath }, "", url.toString());
369532
+ }
369533
+ return true;
369534
+ };
369535
+ // Sync the initial preview page into the URL and kick off the first run.
369260
369536
  (0, react_1.useEffect)(() => {
369261
- if (isPreviewMode) {
369262
- dispatch((0, EditorSlice_1.triggerCodeRun)());
369263
- if (page) {
369264
- setSearchParams((prevParams) => {
369265
- const updatedParams = new URLSearchParams(prevParams);
369266
- updatedParams.set("page", page);
369267
- return updatedParams;
369268
- }, { replace: true });
369269
- }
369537
+ if (!shouldUseBrowserHistory)
369538
+ return;
369539
+ const url = new URL(window.location.href);
369540
+ const pageFromUrl = url.searchParams.get("page");
369541
+ const initialPage = (0, projectPath_1.normalizePath)(pageFromUrl ?? page ?? helpers_1.DEFAULT_ENTRY_FILE_PATH);
369542
+ if (!pageFromUrl) {
369543
+ writePreviewUrlToHistory(initialPage, true);
369544
+ }
369545
+ else {
369546
+ const normalizedUrl = new URL(window.location.href);
369547
+ normalizedUrl.searchParams.set("page", initialPage);
369548
+ window.history.replaceState({ __textJamPreview: true, page: initialPage }, "", normalizedUrl.toString());
369270
369549
  }
369271
- }, [isPreviewMode, page, setSearchParams]);
369550
+ dispatch((0, EditorSlice_1.setPage)(initialPage));
369551
+ dispatch((0, EditorSlice_1.triggerCodeRun)());
369552
+ }, [shouldUseBrowserHistory]);
369553
+ // Browser back/forward: re-run the page encoded in the URL.
369554
+ (0, react_1.useEffect)(() => {
369555
+ if (!shouldUseBrowserHistory)
369556
+ return;
369557
+ const handlePopState = () => {
369558
+ const url = new URL(window.location.href);
369559
+ const pageFromUrl = url.searchParams.get("page");
369560
+ if (!pageFromUrl)
369561
+ return;
369562
+ const normalizedPage = (0, projectPath_1.normalizePath)(pageFromUrl);
369563
+ dispatch((0, EditorSlice_1.setPage)(normalizedPage));
369564
+ dispatch((0, EditorSlice_1.triggerCodeRun)());
369565
+ };
369566
+ window.addEventListener("popstate", handlePopState);
369567
+ return () => {
369568
+ window.removeEventListener("popstate", handlePopState);
369569
+ };
369570
+ }, [shouldUseBrowserHistory]);
369272
369571
  (0, react_1.useEffect)(() => {
369273
369572
  if (isPreviewMode) {
369274
369573
  const handleMessage = (event) => {
@@ -369283,29 +369582,29 @@ function HtmlRunner() {
369283
369582
  };
369284
369583
  }
369285
369584
  }, [isPreviewMode]);
369286
- const showModal = () => {
369287
- dispatch((0, EditorSlice_1.showErrorModal)());
369288
- eventListener();
369289
- };
369290
- const { externalLink, setExternalLink, handleAllowedExternalLink, handleRegularExternalLink, handleExternalLinkError, } = (0, externalLinkHelper_1.useExternalLinkState)(showModal);
369291
- const eventListener = () => {
369292
- window.addEventListener("message", (event) => {
369293
- if (typeof event.data?.msg === "string") {
369294
- if (event.data?.msg === "ERROR: External link") {
369295
- handleExternalLinkError();
369296
- }
369297
- else if (event.data?.msg === "Allowed external link") {
369298
- handleAllowedExternalLink(event.data.payload.linkTo);
369299
- }
369300
- else {
369301
- handleRegularExternalLink(event.data.payload.linkTo);
369302
- }
369303
- }
369304
- });
369305
- };
369585
+ (0, react_1.useEffect)(() => {
369586
+ const handleReloadMessage = (event) => {
369587
+ if (event.data?.msg !== "RELOAD")
369588
+ return;
369589
+ const nextPage = event.data?.payload?.linkTo;
369590
+ if (typeof nextPage !== "string")
369591
+ return;
369592
+ const normalizedPage = (0, projectPath_1.normalizePath)(nextPage);
369593
+ // Push the navigation onto the browser history stack so the native
369594
+ // back/forward buttons (and refresh) navigate preview pages.
369595
+ if (shouldUseBrowserHistoryRef.current ||
369596
+ isPreviewSearch(new URL(window.location.href).searchParams)) {
369597
+ writePreviewUrlToHistory(normalizedPage);
369598
+ }
369599
+ dispatch((0, EditorSlice_1.setPage)(normalizedPage));
369600
+ dispatch((0, EditorSlice_1.triggerCodeRun)());
369601
+ };
369602
+ window.addEventListener("message", handleReloadMessage);
369603
+ return () => window.removeEventListener("message", handleReloadMessage);
369604
+ }, [dispatch]);
369306
369605
  const iframeReload = () => {
369307
369606
  const iframe = output.current?.contentDocument;
369308
- const filePath = (0, helpers_1.getFilenameFromIFrame)(iframe) ?? externalLink;
369607
+ const filePath = (0, helpers_1.getFilenameFromIFrame)(iframe);
369309
369608
  if (runningFilePath !== filePath) {
369310
369609
  setRunningFilePath(filePath);
369311
369610
  }
@@ -369315,9 +369614,11 @@ function HtmlRunner() {
369315
369614
  linkElement.addEventListener("click", (e) => {
369316
369615
  const href = linkElement.getAttribute("href");
369317
369616
  const target = linkElement.getAttribute("target");
369318
- // block in-iframe navigation for links with target="_blank" and .html href
369617
+ if (!href || target !== "_blank" || (0, helpers_1.isExternalUrl)(href))
369618
+ return;
369619
+ // block in-iframe navigation for internal html links with target="_blank"
369319
369620
  // and open them in a preview tab
369320
- if (target === "_blank" && href?.includes(".html")) {
369621
+ if (href?.includes(".html")) {
369321
369622
  e.preventDefault();
369322
369623
  e.stopImmediatePropagation();
369323
369624
  openPreview(`/${href}`);
@@ -369325,15 +369626,15 @@ function HtmlRunner() {
369325
369626
  }, true);
369326
369627
  });
369327
369628
  }
369328
- setExternalLink(null);
369329
369629
  };
369330
369630
  (0, react_1.useEffect)(() => {
369331
- eventListener();
369332
369631
  dispatch((0, EditorSlice_1.loadingRunner)(EditorTypes_1.RunnerType.HTML));
369333
369632
  dispatch((0, EditorSlice_1.setLoadedRunner)(EditorTypes_1.RunnerType.HTML));
369334
369633
  }, []);
369335
369634
  (0, react_1.useEffect)(() => {
369336
369635
  if (codeRunTriggered) {
369636
+ // Set unique key to force iframe remount
369637
+ setIframeKey((prev) => prev + 1);
369337
369638
  runCode();
369338
369639
  if (!isPreviewMode) {
369339
369640
  broadcastChannel.current?.postMessage({
@@ -369342,50 +369643,48 @@ function HtmlRunner() {
369342
369643
  }
369343
369644
  }
369344
369645
  }, [codeRunTriggered, page, isPreviewMode]);
369345
- const currentPageParam = searchParams.get("page");
369346
369646
  (0, react_1.useEffect)(() => {
369347
- if (currentPageParam) {
369348
- dispatch((0, EditorSlice_1.setPage)(currentPageParam));
369349
- }
369350
- }, [currentPageParam, dispatch]);
369647
+ if (!currentPageParam)
369648
+ return;
369649
+ // In preview mode page changes are driven by the browser history flow
369650
+ // (popstate); in regular editor mode keep Redux page in sync with the URL.
369651
+ if (shouldUseBrowserHistory)
369652
+ return;
369653
+ dispatch((0, EditorSlice_1.setPage)(currentPageParam));
369654
+ }, [currentPageParam, shouldUseBrowserHistory, dispatch]);
369351
369655
  const runCode = async () => {
369352
369656
  setConsoleLogs([]);
369353
369657
  const fileToRun = page ?? defaultPreviewFilePath;
369354
369658
  setRunningFilePath(fileToRun);
369355
369659
  dispatch((0, EditorSlice_1.setError)(null));
369356
- if (!externalLink) {
369357
- const entryPoint = (0, helpers_1.getEntryPoint)(projectComponents, fileToRun);
369358
- if (!entryPoint) {
369359
- dispatch((0, EditorSlice_1.setError)(fileToRun === helpers_1.DEFAULT_ENTRY_FILE_PATH && !isPreviewMode
369360
- ? {
369361
- type: Errors_1.ErrorType.ENTRY_FILE_NOT_FOUND,
369362
- details: {
369363
- fileName: helpers_1.DEFAULT_ENTRY_FILE_NAME,
369364
- },
369365
- }
369366
- : {
369367
- type: Errors_1.ErrorType.PAGE_NOT_FOUND,
369368
- }));
369369
- dispatch((0, EditorSlice_1.codeRunHandled)());
369370
- return;
369371
- }
369372
- const indexPage = (0, node_html_parser_1.parse)(entryPoint.content);
369373
- const body = indexPage.querySelector("body") || indexPage;
369374
- const htmlRoot = indexPage.querySelector("html") ?? indexPage;
369375
- body.insertAdjacentHTML("afterbegin", scripts_1.disableLocalStorageScript);
369376
- htmlRoot.insertAdjacentHTML("afterbegin", scripts_1.consoleOverrideScript);
369377
- const { content } = await (0, fileParsers_1.resolveAndRewriteHtmlImports)(indexPage.toString(), projectComponents, entryPoint.path, page ?? entryPoint.path);
369378
- if (output.current) {
369379
- output.current.srcdoc = content;
369380
- }
369381
- if (codeRunTriggered) {
369382
- dispatch((0, EditorSlice_1.codeRunHandled)());
369383
- }
369660
+ const entryPoint = (0, helpers_1.getEntryPoint)(projectComponents, fileToRun);
369661
+ if (!entryPoint) {
369662
+ dispatch((0, EditorSlice_1.setError)(fileToRun === helpers_1.DEFAULT_ENTRY_FILE_PATH && !isPreviewMode
369663
+ ? {
369664
+ type: Errors_1.ErrorType.ENTRY_FILE_NOT_FOUND,
369665
+ details: {
369666
+ fileName: helpers_1.DEFAULT_ENTRY_FILE_NAME,
369667
+ },
369668
+ }
369669
+ : {
369670
+ type: Errors_1.ErrorType.PAGE_NOT_FOUND,
369671
+ }));
369672
+ dispatch((0, EditorSlice_1.codeRunHandled)());
369673
+ return;
369384
369674
  }
369385
- else {
369386
- if (output.current) {
369387
- output.current.src = externalLink;
369388
- }
369675
+ const indexPage = (0, node_html_parser_1.parse)(entryPoint.content);
369676
+ const body = indexPage.querySelector("body") || indexPage;
369677
+ const htmlRoot = indexPage.querySelector("html") ?? indexPage;
369678
+ body.insertAdjacentHTML("afterbegin", scripts_1.disableLocalStorageScript);
369679
+ htmlRoot.insertAdjacentHTML("afterbegin", scripts_1.consoleOverrideScript);
369680
+ const { content } = await (0, fileParsers_1.resolveAndRewriteHtmlImports)(indexPage.toString(), projectComponents, entryPoint.path, page ?? entryPoint.path);
369681
+ if (output.current?.contentWindow?.document) {
369682
+ const doc = output.current.contentWindow.document;
369683
+ doc.open();
369684
+ doc.write(content);
369685
+ doc.close();
369686
+ }
369687
+ if (codeRunTriggered) {
369389
369688
  dispatch((0, EditorSlice_1.codeRunHandled)());
369390
369689
  }
369391
369690
  };
@@ -369425,7 +369724,7 @@ function HtmlRunner() {
369425
369724
  const iframeClasses = (0, classnames_1.default)(styles_module_scss_1.default.iframe, {
369426
369725
  [styles_module_scss_1.default.codeHasBeenRun]: codeHasBeenRun,
369427
369726
  });
369428
- return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.htmlrunnerContainer, children: [(0, jsx_runtime_1.jsx)(OutputTabPanel_1.OutputTabPanel, { title: "Preview", icon: preview_svg_1.default, readOnly: readOnly, extraTabContent: codeHasBeenRun && openInNewTabLink, tabPanelClassName: styles_module_scss_1.default.previewHtml, children: error ? ((0, jsx_runtime_1.jsx)("div", { className: iframeClasses, children: (0, jsx_runtime_1.jsx)(NotFoundPage_1.NotFoundPage, {}) })) : ((0, jsx_runtime_1.jsx)("iframe", { className: iframeClasses, sandbox: "allow-scripts allow-same-origin allow-modals allow-popups", referrerPolicy: "strict-origin-when-cross-origin", allow: "\r\n accelerometer 'none';\r\n camera 'none';\r\n encrypted-media;\r\n fullscreen;\r\n picture-in-picture;\r\n geolocation 'none';\r\n gyroscope 'none';\r\n magnetometer 'none';\r\n microphone 'none';\r\n midi 'none';\r\n payment 'none';\r\n usb 'none';\r\n ", id: "output-frame", title: "HTML Output Preview", ref: output, onLoad: iframeReload })) }), !isPreviewMode && ((0, jsx_runtime_1.jsx)(ResizableWithHandle_1.default, { "data-testid": "proj-console-container", handleDirection: "top", defaultHeight: "50%", className: styles_module_scss_1.default.resizeContainer, handleClassName: styles_module_scss_1.default.resizeHandleContainer, children: (0, jsx_runtime_1.jsx)(OutputTabPanel_1.OutputTabPanel, { title: "Console", icon: console_svg_1.default, readOnly: readOnly, children: (0, jsx_runtime_1.jsx)(HtmlConsole_1.default, { consoleLogs: consoleLogs }) }) }))] }));
369727
+ return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.htmlrunnerContainer, children: [(0, jsx_runtime_1.jsx)(OutputTabPanel_1.OutputTabPanel, { title: "Preview", icon: preview_svg_1.default, readOnly: readOnly, extraTabContent: codeHasBeenRun && openInNewTabLink, tabPanelClassName: styles_module_scss_1.default.previewHtml, children: error ? ((0, jsx_runtime_1.jsx)("div", { className: iframeClasses, children: (0, jsx_runtime_1.jsx)(NotFoundPage_1.NotFoundPage, {}) })) : ((0, jsx_runtime_1.jsx)("iframe", { className: iframeClasses, sandbox: "allow-scripts allow-same-origin allow-modals allow-popups allow-popups-to-escape-sandbox", referrerPolicy: "strict-origin-when-cross-origin", allow: "\r\n accelerometer 'none';\r\n camera 'none';\r\n encrypted-media;\r\n fullscreen;\r\n picture-in-picture;\r\n geolocation 'none';\r\n gyroscope 'none';\r\n magnetometer 'none';\r\n microphone 'none';\r\n midi 'none';\r\n payment 'none';\r\n usb 'none';\r\n ", id: "output-frame", title: "HTML Output Preview", ref: output, onLoad: iframeReload, src: "about:blank" }, iframeKey)) }), !isPreviewMode && ((0, jsx_runtime_1.jsx)(ResizableWithHandle_1.default, { "data-testid": "proj-console-container", handleDirection: "top", defaultHeight: "50%", className: styles_module_scss_1.default.resizeContainer, handleClassName: styles_module_scss_1.default.resizeHandleContainer, children: (0, jsx_runtime_1.jsx)(OutputTabPanel_1.OutputTabPanel, { title: "Console", icon: console_svg_1.default, readOnly: readOnly, children: (0, jsx_runtime_1.jsx)(HtmlConsole_1.default, { consoleLogs: consoleLogs }) }) }))] }));
369429
369728
  }
369430
369729
  exports["default"] = HtmlRunner;
369431
369730
 
@@ -369504,6 +369803,7 @@ const node_html_parser_1 = __webpack_require__(36192);
369504
369803
  const ProjectTypes_1 = __webpack_require__(27130);
369505
369804
  const binaryStore_1 = __webpack_require__(5060);
369506
369805
  const projectHelpers_1 = __webpack_require__(2610);
369806
+ const helpers_1 = __webpack_require__(1108);
369507
369807
  /** Normalizes CSS paths
369508
369808
  * Treats paths without leading / or ./ or ../ as relative to current file
369509
369809
  * E.g. 'image.png' will be treated as './image.png'
@@ -369557,9 +369857,7 @@ const getMimeType = (filePath, fallback = "application/octet-stream") => {
369557
369857
  };
369558
369858
  /** Check if a URL is external, blob, or data */
369559
369859
  const isExternalOrDataUrl = (url) => {
369560
- return (url.startsWith("blob:") ||
369561
- /^(https?:)?\/\//.test(url) ||
369562
- url.startsWith("data:"));
369860
+ return (url.startsWith("blob:") || (0, helpers_1.isExternalUrl)(url) || url.startsWith("data:"));
369563
369861
  };
369564
369862
  async function replaceAsync(input, regex, replacer) {
369565
369863
  let result = "";
@@ -369736,7 +370034,12 @@ async function rewriteSources(indexPage, components, baseFilePath, propName, blo
369736
370034
  const nodes = indexPage.querySelectorAll(`[${propName}]`);
369737
370035
  for (const node of nodes) {
369738
370036
  const raw = node.getAttribute(propName);
369739
- if (!raw || isExternalOrDataUrl(raw))
370037
+ if (!raw)
370038
+ continue;
370039
+ // For external links with target="_blank", add noopener and noreferrer for security
370040
+ if ((0, helpers_1.isExternalUrl)(raw) && node.getAttribute("target") === "_blank")
370041
+ node.setAttribute("rel", "noopener noreferrer");
370042
+ if (isExternalOrDataUrl(raw))
369740
370043
  continue;
369741
370044
  if ((0, projectHelpers_1.parseFileName)(raw).extension === ProjectTypes_1.ProjectFileExtension.HTML &&
369742
370045
  propName === "href") {
@@ -369789,7 +370092,7 @@ async function resolveAndRewriteHtmlImports(html, projectComponents, baseFilePat
369789
370092
 
369790
370093
 
369791
370094
  Object.defineProperty(exports, "__esModule", ({ value: true }));
369792
- exports.getFilenameFromIFrame = exports.getEntryPoint = exports.DEFAULT_ENTRY_FILE_PATH = exports.DEFAULT_ENTRY_FILE_NAME = void 0;
370095
+ exports.isExternalUrl = exports.getFilenameFromIFrame = exports.getEntryPoint = exports.DEFAULT_ENTRY_FILE_PATH = exports.DEFAULT_ENTRY_FILE_NAME = void 0;
369793
370096
  const ProjectTypes_1 = __webpack_require__(27130);
369794
370097
  exports.DEFAULT_ENTRY_FILE_NAME = "index.html";
369795
370098
  exports.DEFAULT_ENTRY_FILE_PATH = `/${exports.DEFAULT_ENTRY_FILE_NAME}`;
@@ -369800,6 +370103,10 @@ const getEntryPoint = (components, filePath) => {
369800
370103
  exports.getEntryPoint = getEntryPoint;
369801
370104
  const getFilenameFromIFrame = (iframe) => iframe?.querySelector("meta[filename]")?.getAttribute("filename") ?? null;
369802
370105
  exports.getFilenameFromIFrame = getFilenameFromIFrame;
370106
+ const isExternalUrl = (url) => {
370107
+ return /^(https?:)?\/\//.test(url);
370108
+ };
370109
+ exports.isExternalUrl = isExternalUrl;
369803
370110
 
369804
370111
 
369805
370112
  /***/ }),
@@ -370111,7 +370418,7 @@ const PyodideRunner = ({ active, packageApiUrl, }) => {
370111
370418
  console.warn("PyodideWorker is not initialized");
370112
370419
  return null;
370113
370420
  }
370114
- return ((0, jsx_runtime_1.jsxs)("div", { className: (0, classnames_1.default)(styles_module_scss_1.default.pythonrunnerContainer, styles_module_scss_1.default.pyodiderunner, active && styles_module_scss_1.default.active), children: [(0, jsx_runtime_1.jsxs)(OutputTabPanel_1.OutputTabPanel, { title: "Terminal", icon: console_svg_1.default, readOnly: readOnly, children: [(0, jsx_runtime_1.jsx)(ErrorMessage_1.default, {}), (0, jsx_runtime_1.jsx)("pre", { className: styles_module_scss_1.default.console, onClick: consoleInput_1.shiftFocusToInput, ref: output })] }), (0, jsx_runtime_1.jsx)(ResizableWithHandle_1.default, { "data-testid": "proj-console-container", handleDirection: "top", defaultHeight: "50%", className: styles_module_scss_1.default.resizeContainer, handleClassName: styles_module_scss_1.default.resizeHandleContainer, children: (0, jsx_runtime_1.jsx)(OutputTabPanel_1.OutputTabPanel, { title: "Visual output", icon: preview_svg_1.default, readOnly: readOnly, children: (0, jsx_runtime_1.jsx)(VisualOutputPane_1.default, { visuals: visuals, setVisuals: setVisuals }) }) })] }));
370421
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, classnames_1.default)(styles_module_scss_1.default.pythonrunnerContainer, styles_module_scss_1.default.pyodiderunner, active && styles_module_scss_1.default.active), children: [(0, jsx_runtime_1.jsxs)(OutputTabPanel_1.OutputTabPanel, { title: "Terminal", icon: console_svg_1.default, readOnly: readOnly, children: [(0, jsx_runtime_1.jsx)(ErrorMessage_1.default, {}), (0, jsx_runtime_1.jsx)("pre", { className: styles_module_scss_1.default.console, onClick: consoleInput_1.shiftFocusToInput, ref: output })] }), (0, jsx_runtime_1.jsx)(ResizableWithHandle_1.default, { "data-testid": "proj-console-container", handleDirection: "top", defaultHeight: "50%", className: styles_module_scss_1.default.resizeContainer, handleClassName: styles_module_scss_1.default.resizeHandleContainer, children: (0, jsx_runtime_1.jsx)(OutputTabPanel_1.OutputTabPanel, { title: "VNC", icon: preview_svg_1.default, readOnly: readOnly, children: (0, jsx_runtime_1.jsx)(VisualOutputPane_1.default, { visuals: visuals, setVisuals: setVisuals }) }) })] }));
370115
370422
  };
370116
370423
  exports["default"] = PyodideRunner;
370117
370424
 
@@ -370131,6 +370438,7 @@ const react_1 = __webpack_require__(51649);
370131
370438
  const highcharts_1 = __importDefault(__webpack_require__(14783));
370132
370439
  const plotly_js_1 = __importDefault(__webpack_require__(28850));
370133
370440
  const styles_module_scss_1 = __importDefault(__webpack_require__(12914));
370441
+ const VisualOutputPaneEmpty_1 = __importDefault(__webpack_require__(93290));
370134
370442
  const VisualOutputPane = ({ visuals, setVisuals }) => {
370135
370443
  const output = (0, react_1.useRef)(null);
370136
370444
  const showVisual = (0, react_1.useCallback)((visual, output) => {
@@ -370192,7 +370500,7 @@ const VisualOutputPane = ({ visuals, setVisuals }) => {
370192
370500
  setVisuals((visuals) => showVisuals(visuals, output));
370193
370501
  }
370194
370502
  }, [visuals, setVisuals, showVisuals]);
370195
- return ((0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.visualOutput, children: (0, jsx_runtime_1.jsx)("div", { ref: output, className: styles_module_scss_1.default.pythonGraphic }) }));
370503
+ return ((0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.visualOutput, children: visuals.length !== 0 ? ((0, jsx_runtime_1.jsx)("div", { ref: output, className: styles_module_scss_1.default.pythonGraphic })) : ((0, jsx_runtime_1.jsx)(VisualOutputPaneEmpty_1.default, {})) }));
370196
370504
  };
370197
370505
  const elementFromProps = (map) => {
370198
370506
  const tag = map.get("tag");
@@ -370211,6 +370519,27 @@ const elementFromProps = (map) => {
370211
370519
  exports["default"] = VisualOutputPane;
370212
370520
 
370213
370521
 
370522
+ /***/ }),
370523
+
370524
+ /***/ 93290:
370525
+ /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
370526
+
370527
+
370528
+ var __importDefault = (this && this.__importDefault) || function (mod) {
370529
+ return (mod && mod.__esModule) ? mod : { "default": mod };
370530
+ };
370531
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
370532
+ const jsx_runtime_1 = __webpack_require__(74848);
370533
+ const styles_module_scss_1 = __importDefault(__webpack_require__(38231));
370534
+ const monitor_svg_1 = __importDefault(__webpack_require__(36311));
370535
+ const SvgIcon_1 = __webpack_require__(82917);
370536
+ const Text_1 = __webpack_require__(82803);
370537
+ const VisualOutputPaneEmpty = () => {
370538
+ return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.container, children: [(0, jsx_runtime_1.jsx)(SvgIcon_1.SvgIcon, { SvgElement: monitor_svg_1.default, size: 32, "aria-hidden": "true", focusable: "false" }), (0, jsx_runtime_1.jsx)(Text_1.Text, { size: 20, className: styles_module_scss_1.default.title, weight: "bold", children: "View your app\u2019s desktop screen output" }), (0, jsx_runtime_1.jsx)(Text_1.Text, { size: 12, children: "No VNC browser is currently running with compatible ports available to view." })] }));
370539
+ };
370540
+ exports["default"] = VisualOutputPaneEmpty;
370541
+
370542
+
370214
370543
  /***/ }),
370215
370544
 
370216
370545
  /***/ 85799:
@@ -371613,16 +371942,55 @@ const SvgIcon_1 = __webpack_require__(82917);
371613
371942
  const arrow_right_svg_1 = __importDefault(__webpack_require__(17297));
371614
371943
  const styles_module_scss_1 = __importDefault(__webpack_require__(51191));
371615
371944
  const ContextMenu_1 = __importDefault(__webpack_require__(39179));
371945
+ const Tooltip_1 = __importDefault(__webpack_require__(26982));
371946
+ const useHover_1 = __webpack_require__(78556);
371616
371947
  const Dropdown = (props) => {
371617
371948
  const { ButtonIcon, buttonClassname, menuOptions, menuPosition, ariaLabel } = props;
371618
371949
  const { direction = "bottom", align = "center" } = menuPosition || {};
371619
371950
  const [isOpen, setOpen] = (0, react_1.useState)(false);
371620
371951
  const buttonRef = (0, react_1.useRef)(null);
371621
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", "aria-label": ariaLabel, className: (0, classnames_1.default)(buttonClassname, isOpen && styles_module_scss_1.default.buttonActive), onClick: () => setOpen((prev) => !prev), ref: buttonRef, children: [ButtonIcon && ((0, jsx_runtime_1.jsx)(SvgIcon_1.SvgIcon, { SvgElement: ButtonIcon, size: 16, "aria-hidden": "true", focusable: "false" })), (0, jsx_runtime_1.jsx)(SvgIcon_1.SvgIcon, { className: (0, classnames_1.default)(styles_module_scss_1.default.arrowIcon, isOpen && styles_module_scss_1.default.arrowIconOpen), style: { rotate: `${isOpen ? "-" : ""}90deg` }, SvgElement: arrow_right_svg_1.default, size: 8, "aria-hidden": "true", focusable: "false" })] }), (0, jsx_runtime_1.jsx)(ContextMenu_1.default, { anchorRef: buttonRef, menuOptions: menuOptions, opened: isOpen, onClose: () => setOpen(false), align: align, direction: direction, gap: 4 })] }));
371952
+ const { hovered, hoverProps } = (0, useHover_1.useHover)();
371953
+ return ((0, jsx_runtime_1.jsxs)("div", { ...hoverProps, children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", "aria-label": ariaLabel, className: (0, classnames_1.default)(buttonClassname, isOpen && styles_module_scss_1.default.buttonActive), onClick: () => setOpen((prev) => !prev), ref: buttonRef, children: [ButtonIcon && ((0, jsx_runtime_1.jsx)(SvgIcon_1.SvgIcon, { SvgElement: ButtonIcon, size: 16, "aria-hidden": "true", focusable: "false" })), (0, jsx_runtime_1.jsx)(SvgIcon_1.SvgIcon, { className: (0, classnames_1.default)(styles_module_scss_1.default.arrowIcon, isOpen && styles_module_scss_1.default.arrowIconOpen), style: { rotate: `${isOpen ? "-" : ""}90deg` }, SvgElement: arrow_right_svg_1.default, size: 8, "aria-hidden": "true", focusable: "false" })] }), (0, jsx_runtime_1.jsx)(Tooltip_1.default, { message: "Upload files", visible: hovered && !isOpen, position: "fixed" }), (0, jsx_runtime_1.jsx)(ContextMenu_1.default, { anchorRef: buttonRef, menuOptions: menuOptions, opened: isOpen, onClose: () => setOpen(false), align: align, direction: direction, gap: 4 })] }));
371622
371954
  };
371623
371955
  exports["default"] = Dropdown;
371624
371956
 
371625
371957
 
371958
+ /***/ }),
371959
+
371960
+ /***/ 20186:
371961
+ /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
371962
+
371963
+
371964
+ var __importDefault = (this && this.__importDefault) || function (mod) {
371965
+ return (mod && mod.__esModule) ? mod : { "default": mod };
371966
+ };
371967
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
371968
+ const jsx_runtime_1 = __webpack_require__(74848);
371969
+ const react_1 = __webpack_require__(51649);
371970
+ const SidebarPanel_1 = __importDefault(__webpack_require__(83617));
371971
+ const styles_module_scss_1 = __importDefault(__webpack_require__(99016));
371972
+ const PANEL_FRACTION = 0.33;
371973
+ const getPanelWidth = () => `${Math.round(window.innerWidth * PANEL_FRACTION)}px`;
371974
+ const getMaxPanelWidth = () => `${Math.round(window.innerWidth * 2 * PANEL_FRACTION)}px`;
371975
+ const CurriculumPanel = ({ curriculumContent, }) => {
371976
+ const [defaultWidth, setDefaultWidth] = (0, react_1.useState)(getPanelWidth);
371977
+ const [maxWidth, setMaxWidth] = (0, react_1.useState)(getMaxPanelWidth);
371978
+ (0, react_1.useEffect)(() => {
371979
+ const update = () => {
371980
+ setDefaultWidth(getPanelWidth());
371981
+ setMaxWidth(getMaxPanelWidth());
371982
+ };
371983
+ window.addEventListener("resize", update);
371984
+ return () => window.removeEventListener("resize", update);
371985
+ }, []);
371986
+ if (!curriculumContent) {
371987
+ return null;
371988
+ }
371989
+ return ((0, jsx_runtime_1.jsx)(SidebarPanel_1.default, { panelContentClassName: styles_module_scss_1.default.panelContent, className: styles_module_scss_1.default.curriculumPanel, defaultWidth: defaultWidth, maxWidth: maxWidth, children: curriculumContent }));
371990
+ };
371991
+ exports["default"] = CurriculumPanel;
371992
+
371993
+
371626
371994
  /***/ }),
371627
371995
 
371628
371996
  /***/ 7802:
@@ -371693,10 +372061,15 @@ const FileTreeContext_1 = __webpack_require__(5323);
371693
372061
  const ProjectTypes_1 = __webpack_require__(27130);
371694
372062
  const Text_1 = __webpack_require__(82803);
371695
372063
  const stores_1 = __webpack_require__(32132);
372064
+ const Tooltip_1 = __importDefault(__webpack_require__(26982));
372065
+ const useHover_1 = __webpack_require__(78556);
371696
372066
  const FileTreeActions = ({ hasExpandedNodes, }) => {
371697
372067
  const { expandAll, collapseAll, createComponent, importFiles, importFolder } = (0, react_1.useContext)(FileTreeContext_1.TreeContext);
371698
372068
  const isReadOnly = (0, stores_1.useAppSelector)((state) => state.editor.readOnly);
371699
- return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.fileTreeActions, children: [hasExpandedNodes ? ((0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "Collapse all folders", onClick: collapseAll, className: styles_module_scss_1.default.iconButton, children: (0, jsx_runtime_1.jsx)(minus_circle_svg_1.default, { "aria-hidden": "true", focusable: "false" }) })) : ((0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "Expand all folders", onClick: expandAll, className: styles_module_scss_1.default.iconButton, children: (0, jsx_runtime_1.jsx)(plus_circle_svg_1.default, { "aria-hidden": "true", focusable: "false" }) })), (0, jsx_runtime_1.jsx)(Text_1.Text, { size: 12, weight: "medium", className: styles_module_scss_1.default.fileTreeActionsLabel, children: "Project files" }), !isReadOnly && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "Add folder", onClick: () => createComponent(ProjectTypes_1.ProjectComponentType.DIR), className: (0, classnames_1.default)([styles_module_scss_1.default.iconButtonFilled, styles_module_scss_1.default.iconButton]), children: (0, jsx_runtime_1.jsx)(add_folder_svg_1.default, { "aria-hidden": "true", focusable: "false" }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "Add file", onClick: () => createComponent(ProjectTypes_1.ProjectComponentType.FILE), className: (0, classnames_1.default)([styles_module_scss_1.default.iconButtonFilled, styles_module_scss_1.default.iconButton]), children: (0, jsx_runtime_1.jsx)(add_file_svg_1.default, { "aria-hidden": "true", focusable: "false" }) }), (0, jsx_runtime_1.jsx)(ImportButton_1.ImportButton, { importFiles: importFiles, importFolder: importFolder })] }))] }));
372069
+ const { hovered: toggleHovered, hoverProps: toggleHoverProps } = (0, useHover_1.useHover)();
372070
+ const { hovered: addFolderHovered, hoverProps: addFolderHoverProps } = (0, useHover_1.useHover)();
372071
+ const { hovered: addFileHovered, hoverProps: addFileHoverProps } = (0, useHover_1.useHover)();
372072
+ return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.fileTreeActions, children: [hasExpandedNodes ? ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.tooltipWrapper, ...toggleHoverProps, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "Collapse all folders", onClick: collapseAll, className: styles_module_scss_1.default.iconButton, children: (0, jsx_runtime_1.jsx)(minus_circle_svg_1.default, { "aria-hidden": "true", focusable: "false" }) }), (0, jsx_runtime_1.jsx)(Tooltip_1.default, { message: "Collapse all folders", visible: toggleHovered, position: "fixed" })] })) : ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.tooltipWrapper, ...toggleHoverProps, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "Expand all folders", onClick: expandAll, className: styles_module_scss_1.default.iconButton, children: (0, jsx_runtime_1.jsx)(plus_circle_svg_1.default, { "aria-hidden": "true", focusable: "false" }) }), (0, jsx_runtime_1.jsx)(Tooltip_1.default, { message: "Expand all folders", visible: toggleHovered, position: "fixed" })] })), (0, jsx_runtime_1.jsx)(Text_1.Text, { size: 12, weight: "medium", className: styles_module_scss_1.default.fileTreeActionsLabel, children: "Project files" }), !isReadOnly && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.tooltipWrapper, ...addFolderHoverProps, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "Add folder", onClick: () => createComponent(ProjectTypes_1.ProjectComponentType.DIR), className: (0, classnames_1.default)([styles_module_scss_1.default.iconButtonFilled, styles_module_scss_1.default.iconButton]), children: (0, jsx_runtime_1.jsx)(add_folder_svg_1.default, { "aria-hidden": "true", focusable: "false" }) }), (0, jsx_runtime_1.jsx)(Tooltip_1.default, { message: "Add folder", visible: addFolderHovered, position: "fixed" })] }), (0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.tooltipWrapper, ...addFileHoverProps, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "Add file", onClick: () => createComponent(ProjectTypes_1.ProjectComponentType.FILE), className: (0, classnames_1.default)([styles_module_scss_1.default.iconButtonFilled, styles_module_scss_1.default.iconButton]), children: (0, jsx_runtime_1.jsx)(add_file_svg_1.default, { "aria-hidden": "true", focusable: "false" }) }), (0, jsx_runtime_1.jsx)(Tooltip_1.default, { message: "Add file", visible: addFileHovered, position: "fixed" })] }), (0, jsx_runtime_1.jsx)(ImportButton_1.ImportButton, { importFiles: importFiles, importFolder: importFolder })] }))] }));
371700
372073
  };
371701
372074
  exports.FileTreeActions = FileTreeActions;
371702
372075
 
@@ -371841,9 +372214,15 @@ const DeleteOption_1 = __webpack_require__(50185);
371841
372214
  const react_redux_1 = __webpack_require__(14062);
371842
372215
  const EditorSlice_1 = __webpack_require__(68512);
371843
372216
  const types_1 = __webpack_require__(92932);
372217
+ const createProjectArchive_1 = __webpack_require__(46514);
372218
+ const js_convert_case_1 = __webpack_require__(61647);
372219
+ const stores_1 = __webpack_require__(32132);
372220
+ const file_saver_1 = __importDefault(__webpack_require__(4213));
372221
+ const sendToast_1 = __webpack_require__(50068);
371844
372222
  const ItemContextMenu = ({ item, menuPosition, isOpened, onClose, openImportFileDialog, }) => {
371845
372223
  const dispatch = (0, react_redux_1.useDispatch)();
371846
372224
  const { startRenaming, createComponent, downloadFile, removeComponent } = (0, react_1.useContext)(FileTreeContext_1.TreeContext);
372225
+ const project = (0, stores_1.useAppSelector)((state) => state.editor.project);
371847
372226
  const handleDelete = ({ isFolder }) => {
371848
372227
  dispatch((0, EditorSlice_1.showModal)({
371849
372228
  modal: types_1.ModalType.REMOVE_ITEM,
@@ -371877,6 +372256,25 @@ const ItemContextMenu = ({ item, menuPosition, isOpened, onClose, openImportFile
371877
372256
  icon: (0, jsx_runtime_1.jsx)(OptionIcon_1.OptionIcon, { icon: upload_file_svg_1.default }),
371878
372257
  action: openImportFileDialog,
371879
372258
  },
372259
+ {
372260
+ text: "Download Folder",
372261
+ icon: (0, jsx_runtime_1.jsx)(OptionIcon_1.OptionIcon, { icon: download_svg_1.default }),
372262
+ // Sync action that fires-and-handles its own async work. `ContextMenu`
372263
+ // calls `option.action?.()` without awaiting, so we must catch errors
372264
+ // here ourselves to avoid an unhandled promise rejection.
372265
+ action: () => {
372266
+ const folder = item.getItemData();
372267
+ const fileName = `${(0, js_convert_case_1.toSnakeCase)(project.name || "untitled_project")}_${(0, js_convert_case_1.toSnakeCase)(folder.name || "folder")}.zip`;
372268
+ const downloadFolder = async () => {
372269
+ const { zipBlob: content } = await (0, createProjectArchive_1.createProjectArchive)(project, folder);
372270
+ file_saver_1.default.saveAs(content, fileName);
372271
+ };
372272
+ downloadFolder().catch((err) => {
372273
+ console.error("Download Folder failed:", err);
372274
+ (0, sendToast_1.showError)(`Failed to download folder "${folder.name}".`);
372275
+ });
372276
+ },
372277
+ },
371880
372278
  {
371881
372279
  text: (0, jsx_runtime_1.jsx)(DeleteOption_1.DeleteOption, {}),
371882
372280
  action: () => handleDelete({ isFolder: true }),
@@ -372523,12 +372921,21 @@ const home_svg_1 = __importDefault(__webpack_require__(69046));
372523
372921
  const AuthContext_1 = __webpack_require__(55471);
372524
372922
  // commented out for now as it should be introduced in the later phases
372525
372923
  // import SettingsPanel from "./SettingsPanel/SettingsPanel";
372526
- // import ActivityIcon from "../../../assets/icons/activity.svg";
372924
+ const activity_svg_1 = __importDefault(__webpack_require__(50424));
372527
372925
  // import FontIcon from "../../../assets/icons/font.svg";
372528
372926
  // import PlaygroundIcon from "../../../assets/icons/playground.svg";
372529
372927
  // import ThemeIcon from "../../../assets/icons/theme.svg";
372530
372928
  const styles_module_scss_1 = __importDefault(__webpack_require__(21852));
372531
- const getSidebarOptions = (initials, homeAction, isCodeVisible) => [
372929
+ const CurriculumPanel_1 = __importDefault(__webpack_require__(20186));
372930
+ const types_1 = __webpack_require__(92932);
372931
+ const EditorSlice_1 = __webpack_require__(68512);
372932
+ const react_redux_1 = __webpack_require__(14062);
372933
+ var PanelNames;
372934
+ (function (PanelNames) {
372935
+ PanelNames["FILES"] = "files";
372936
+ PanelNames["CURRICULUM"] = "activity";
372937
+ })(PanelNames || (PanelNames = {}));
372938
+ const getSidebarOptions = (initials, homeAction, isCodeVisible, curriculumComponent) => [
372532
372939
  {
372533
372940
  name: "home",
372534
372941
  icon: () => (0, jsx_runtime_1.jsx)(SvgIcon_1.SvgIcon, { SvgElement: home_svg_1.default, size: 22 }),
@@ -372539,7 +372946,7 @@ const getSidebarOptions = (initials, homeAction, isCodeVisible) => [
372539
372946
  ...(isCodeVisible
372540
372947
  ? [
372541
372948
  {
372542
- name: "files",
372949
+ name: PanelNames.FILES,
372543
372950
  icon: () => (0, jsx_runtime_1.jsx)(SvgIcon_1.SvgIcon, { SvgElement: folder_svg_1.default, size: 24 }),
372544
372951
  title: "Files",
372545
372952
  position: "top",
@@ -372547,6 +372954,17 @@ const getSidebarOptions = (initials, homeAction, isCodeVisible) => [
372547
372954
  },
372548
372955
  ]
372549
372956
  : []),
372957
+ ...(!!curriculumComponent
372958
+ ? [
372959
+ {
372960
+ name: PanelNames.CURRICULUM,
372961
+ icon: () => (0, jsx_runtime_1.jsx)(SvgIcon_1.SvgIcon, { SvgElement: activity_svg_1.default, size: 24 }),
372962
+ title: "Curriculum",
372963
+ position: "top",
372964
+ panel: () => ((0, jsx_runtime_1.jsx)(CurriculumPanel_1.default, { curriculumContent: curriculumComponent })),
372965
+ },
372966
+ ]
372967
+ : []),
372550
372968
  ...(initials?.length
372551
372969
  ? [
372552
372970
  {
@@ -372558,20 +372976,32 @@ const getSidebarOptions = (initials, homeAction, isCodeVisible) => [
372558
372976
  ]
372559
372977
  : []),
372560
372978
  ];
372561
- const Sidebar = ({ options = [] }) => {
372979
+ const Sidebar = ({ options = [], curriculumComponent = null, }) => {
372980
+ const dispatch = (0, react_redux_1.useDispatch)();
372562
372981
  const initials = (0, stores_1.useAppSelector)((state) => state.user.initials);
372563
372982
  const isCodeVisible = (0, stores_1.useAppSelector)((state) => state.editor.isCodeVisible);
372983
+ const curriculumDisplayMode = (0, stores_1.useAppSelector)((state) => state.editor.curriculumDisplayMode);
372564
372984
  const { onHome } = (0, AuthContext_1.useAuthActions)();
372565
- const sideBarOptions = getSidebarOptions(initials, onHome, isCodeVisible);
372985
+ const sideBarOptions = getSidebarOptions(initials, onHome, isCodeVisible, curriculumComponent);
372566
372986
  const menuOptions = sideBarOptions.filter((option) => options.includes(option.name));
372567
- const [option, setOption] = (0, react_1.useState)(null);
372987
+ const [option, setOption] = (0, react_1.useState)(curriculumDisplayMode !== types_1.CurriculumDisplayMode.HIDDEN
372988
+ ? PanelNames.CURRICULUM
372989
+ : null);
372568
372990
  const toggleOption = (newOption) => {
372569
372991
  // when toggle the same option, close panel
372570
372992
  if (option === newOption) {
372571
372993
  setOption(null);
372994
+ // State of curriculum control must be set because it can be changed from contentSection option
372995
+ if (newOption === PanelNames.CURRICULUM) {
372996
+ dispatch((0, EditorSlice_1.setCurriculumDisplayMode)(types_1.CurriculumDisplayMode.HIDDEN));
372997
+ }
372572
372998
  }
372573
372999
  else {
372574
373000
  setOption(newOption);
373001
+ // State of curriculum control must be set because it can be changed from contentSection option
373002
+ if (newOption === PanelNames.CURRICULUM) {
373003
+ dispatch((0, EditorSlice_1.setCurriculumDisplayMode)(types_1.CurriculumDisplayMode.NORMAL));
373004
+ }
372575
373005
  }
372576
373006
  };
372577
373007
  const activeOption = menuOptions.find((opt) => opt.name === option);
@@ -372621,20 +373051,27 @@ const classnames_1 = __importDefault(__webpack_require__(46942));
372621
373051
  const styles_module_scss_1 = __importDefault(__webpack_require__(21852));
372622
373052
  const UserMenu_1 = __importDefault(__webpack_require__(2957));
372623
373053
  const backgroundColors = [
372624
- 'D7F9F4', '43D6B9',
372625
- '0D9C90', 'E5F8FF',
372626
- 'FEEFEE', 'F37C6F',
372627
- 'FFF3E8', 'F9B88C',
372628
- 'FA7815', 'FCE688'
373054
+ "D7F9F4",
373055
+ "43D6B9",
373056
+ "0D9C90",
373057
+ "E5F8FF",
373058
+ "FEEFEE",
373059
+ "F37C6F",
373060
+ "FFF3E8",
373061
+ "F9B88C",
373062
+ "FA7815",
373063
+ "FCE688",
372629
373064
  ];
372630
373065
  const SidebarBarOption = (props) => {
372631
- const { Icon, isActive, name, title, toggleOption, buttonText, isUser, action } = props;
373066
+ const { Icon, isActive, name, title, toggleOption, buttonText, isUser, action, } = props;
372632
373067
  const userId = (0, stores_1.useAppSelector)((state) => state.user.userId);
372633
373068
  const isFacilitator = (0, stores_1.useAppSelector)((state) => state.user.isFacilitator);
372634
373069
  const avatarSrc = (0, stores_1.useAppSelector)((state) => state.user.avatarSrc);
372635
373070
  const avatarStyle = isUser
372636
373071
  ? isFacilitator
372637
- ? { background: 'linear-gradient(90deg,#d7f9f4,#fff9e2,#fff3e8,#e5f8ff,#d7f9f4)' }
373072
+ ? {
373073
+ background: "linear-gradient(90deg,#d7f9f4,#fff9e2,#fff3e8,#e5f8ff,#d7f9f4)",
373074
+ }
372638
373075
  : { backgroundColor: `#${backgroundColors[(userId ?? 0) % 10]}` }
372639
373076
  : null;
372640
373077
  const [isMenuOpen, setIsMenuOpen] = (0, react_1.useState)(false);
@@ -373267,7 +373704,7 @@ const DefaultMZCriteria_1 = __webpack_require__(29441);
373267
373704
  const WebComponentCustomEvents_1 = __webpack_require__(53841);
373268
373705
  const styles_module_scss_1 = __importDefault(__webpack_require__(93105));
373269
373706
  const stores_1 = __webpack_require__(32132);
373270
- const WebComponentProject = ({ nameEditable = false, sidebarOptions = [], packageApiUrl, }) => {
373707
+ const WebComponentProject = ({ nameEditable = false, sidebarOptions = [], packageApiUrl, curriculumComponent = null, }) => {
373271
373708
  const loading = (0, stores_1.useAppSelector)((state) => state.editor.loading);
373272
373709
  const project = (0, stores_1.useAppSelector)((state) => state.editor.project);
373273
373710
  const outputOnly = (0, stores_1.useAppSelector)((state) => state.editor.isOutputOnly);
@@ -373303,7 +373740,7 @@ const WebComponentProject = ({ nameEditable = false, sidebarOptions = [], packag
373303
373740
  document.dispatchEvent((0, WebComponentCustomEvents_1.runCompletedEvent)(payload));
373304
373741
  }
373305
373742
  }, [codeRunTriggered, codeHasRun, outputOnly, error]);
373306
- return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: outputOnly ? ((0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.outputPreview, children: loading === "success" && (0, jsx_runtime_1.jsx)(Output_1.default, { packageApiUrl: packageApiUrl }) })) : ((0, jsx_runtime_1.jsx)(Project_1.default, { nameEditable: nameEditable, sidebarOptions: sidebarOptions, packageApiUrl: packageApiUrl })) }));
373743
+ return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: outputOnly ? ((0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.outputPreview, children: loading === "success" && (0, jsx_runtime_1.jsx)(Output_1.default, { packageApiUrl: packageApiUrl }) })) : ((0, jsx_runtime_1.jsx)(Project_1.default, { curriculumComponent: curriculumComponent, nameEditable: nameEditable, sidebarOptions: sidebarOptions, packageApiUrl: packageApiUrl })) }));
373307
373744
  };
373308
373745
  exports["default"] = WebComponentProject;
373309
373746
 
@@ -373415,7 +373852,7 @@ const stores_1 = __webpack_require__(32132);
373415
373852
  const SaveBeforeLeave_1 = __webpack_require__(47318);
373416
373853
  const selectors_1 = __webpack_require__(43551);
373417
373854
  const WebComponentLoader = (props) => {
373418
- const { locale = "en", projectNameEditable = true, readOnly = false, sidebarOptions = [], project: projectData, saveProject, isSaving, loadProjectContent, projectContent = null, starterCodeContent = null, isLoading = false, packageApiUrl, user, isCodeVisible = true, isSharedProject = false, shareLinks, isContentLoaded = null, isSaveSuccess = null, loadTemplateProjectData, isTemplateDataLoaded = false, } = props;
373855
+ const { locale = "en", projectNameEditable = true, readOnly = false, sidebarOptions = [], project: projectData, saveProject, isSaving, loadProjectContent, projectContent = null, starterCodeContent = null, isLoading = false, packageApiUrl, user, isCodeVisible = true, isSharedProject = false, shareLinks, isContentLoaded = null, isSaveSuccess = null, loadTemplateProjectData, isTemplateDataLoaded = false, curriculum, } = props;
373419
373856
  const dispatch = (0, react_redux_1.useDispatch)();
373420
373857
  const { t } = (0, react_i18next_1.useTranslation)();
373421
373858
  const [searchParams] = (0, react_router_dom_1.useSearchParams)();
@@ -373505,6 +373942,9 @@ const WebComponentLoader = (props) => {
373505
373942
  (0, react_1.useEffect)(() => {
373506
373943
  dispatch((0, EditorSlice_1.setReadOnly)(readOnly));
373507
373944
  }, [readOnly, dispatch]);
373945
+ (0, react_1.useEffect)(() => {
373946
+ dispatch((0, EditorSlice_1.setCurriculumDisplayMode)(curriculum?.curriculumDisplayMode ?? types_1.CurriculumDisplayMode.HIDDEN));
373947
+ }, [curriculum, dispatch]);
373508
373948
  (0, react_1.useEffect)(() => {
373509
373949
  if (isCodeVisible !== undefined) {
373510
373950
  dispatch((0, EditorSlice_1.setCodeVisibility)({ isVisible: isCodeVisible }));
@@ -373555,18 +373995,11 @@ const WebComponentLoader = (props) => {
373555
373995
  const renderSuccessState = () => ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsxs)(settings_1.SettingsContext.Provider, { value: {
373556
373996
  theme: themeDefault,
373557
373997
  fontSize: "small",
373558
- }, children: [(0, jsx_runtime_1.jsx)(ToastMessages_1.ToastMessagesProvider, {}), (0, jsx_runtime_1.jsxs)("div", { id: "textjam-root", className: `editor-shell --${themeDefault}`, children: [(0, jsx_runtime_1.jsx)(WebComponentProject_1.default, { nameEditable: projectNameEditable, sidebarOptions: sidebarOptions, packageApiUrl: packageApiUrl }), errorModalShowing && (0, jsx_runtime_1.jsx)(ErrorModal_1.default, {}), modal, shouldBlockNavigation && (0, jsx_runtime_1.jsx)(SaveBeforeLeave_1.SaveBeforeLeaveModal, {})] })] }) }));
373998
+ }, children: [(0, jsx_runtime_1.jsx)(ToastMessages_1.ToastMessagesProvider, {}), (0, jsx_runtime_1.jsxs)("div", { id: "textjam-root", className: `editor-shell --${themeDefault}`, children: [(0, jsx_runtime_1.jsx)(WebComponentProject_1.default, { curriculumComponent: curriculum?.curriculumContent ?? null, nameEditable: projectNameEditable, sidebarOptions: sidebarOptions, packageApiUrl: packageApiUrl }), errorModalShowing && (0, jsx_runtime_1.jsx)(ErrorModal_1.default, {}), modal, shouldBlockNavigation && (0, jsx_runtime_1.jsx)(SaveBeforeLeave_1.SaveBeforeLeaveModal, {})] })] }) }));
373559
373999
  const renderFailedState = () => ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsx)("p", { children: t("webComponent.failed") }) }));
373560
374000
  const renderLoadingState = () => (0, jsx_runtime_1.jsx)(Loading_1.default, {});
373561
- if (loading === types_1.LoadingState.SUCCESS) {
373562
- return renderSuccessState();
373563
- }
373564
- else if (loading === types_1.LoadingState.FAILED) {
373565
- return renderFailedState();
373566
- }
373567
- else {
373568
- return renderLoadingState();
373569
- }
374001
+ const isLoadingState = ![types_1.LoadingState.SUCCESS, types_1.LoadingState.FAILED].includes(loading);
374002
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [loading === types_1.LoadingState.SUCCESS && renderSuccessState(), loading === types_1.LoadingState.FAILED && renderFailedState(), isLoadingState && renderLoadingState()] }));
373570
374003
  };
373571
374004
  exports["default"] = WebComponentLoader;
373572
374005
 
@@ -374179,10 +374612,10 @@ const getUniqueNameFromItem = (childrenNames, baseName, extension = "") => {
374179
374612
  // if there is a conflict, use canonical lowercase for the duplicate
374180
374613
  const normalizedBaseName = baseName.toLowerCase();
374181
374614
  let counter = 1;
374182
- let candidate = `${normalizedBaseName}(${counter})`;
374615
+ let candidate = `${normalizedBaseName} (${counter})`;
374183
374616
  while (existingNamesLower.has(fullName(candidate).toLowerCase())) {
374184
374617
  counter++;
374185
- candidate = `${normalizedBaseName}(${counter})`;
374618
+ candidate = `${normalizedBaseName} (${counter})`;
374186
374619
  }
374187
374620
  return candidate;
374188
374621
  };
@@ -375242,7 +375675,7 @@ exports.useUnsavedDraftResolution = useUnsavedDraftResolution;
375242
375675
  var _a;
375243
375676
  Object.defineProperty(exports, "__esModule", ({ value: true }));
375244
375677
  exports.setRemixTriggered = exports.setIsLoadingCommit = exports.setCommitIdLoadTriggered = exports.setShareLinks = exports.setCommits = exports.setSharedStatus = exports.setCodeVisibility = exports.applyComponentsPatch = exports.setSaving = exports.disableTheming = exports.hideSidebar = exports.showSidebar = exports.closeModal = exports.showModal = exports.closeErrorModal = exports.showErrorModal = exports.updateProjectName = exports.setProjectNameDraft = exports.setSaveTriggered = exports.triggerDraw = exports.triggerCodeRun = exports.stopDraw = exports.stopCodeRun = exports.setLoading = exports.setReadOnly = exports.updateProjectIdentifier = exports.updateProjectCommits = exports.updateProjectSnapshot = exports.setProject = exports.setHasShownSavePrompt = exports.setError = exports.setCascadeUpdate = exports.setIsOutputOnly = exports.setAutorunEnabled = exports.setPage = exports.setFocussedFileIndex = exports.addFilePanel = exports.setOpenedFiles = exports.openFile = exports.closeFile = exports.expireJustLoaded = exports.codeRunHandled = exports.resetRunner = exports.setLoadedRunner = exports.loadingRunner = exports.updateProjectComponent = exports.addProjectComponent = exports.resetState = exports.EditorSlice = exports.editorInitialState = void 0;
375245
- exports.queueBinaryWrites = exports.revertProject = exports.setShouldBypassCachedDraft = void 0;
375678
+ exports.queueBinaryWrites = exports.setCurriculumDisplayMode = exports.revertProject = exports.setShouldBypassCachedDraft = void 0;
375246
375679
  const toolkit_1 = __webpack_require__(12069);
375247
375680
  const ProjectTypes_1 = __webpack_require__(27130);
375248
375681
  const types_1 = __webpack_require__(92932);
@@ -375291,6 +375724,7 @@ exports.editorInitialState = {
375291
375724
  shareLinks: null,
375292
375725
  remixTriggered: false,
375293
375726
  projectNameDraft: null,
375727
+ curriculumDisplayMode: types_1.CurriculumDisplayMode.HIDDEN,
375294
375728
  shouldBypassCachedDraft: false,
375295
375729
  };
375296
375730
  exports.EditorSlice = (0, toolkit_1.createSlice)({
@@ -375414,6 +375848,9 @@ exports.EditorSlice = (0, toolkit_1.createSlice)({
375414
375848
  expireJustLoaded: (state) => {
375415
375849
  state.justLoaded = false;
375416
375850
  },
375851
+ setCurriculumDisplayMode: (state, action) => {
375852
+ state.curriculumDisplayMode = action.payload;
375853
+ },
375417
375854
  setReadOnly: (state, action) => {
375418
375855
  state.readOnly = action.payload;
375419
375856
  },
@@ -375580,7 +376017,7 @@ exports.EditorSlice = (0, toolkit_1.createSlice)({
375580
376017
  },
375581
376018
  });
375582
376019
  // Action creators are generated for each case reducer function
375583
- _a = exports.EditorSlice.actions, exports.resetState = _a.resetState, exports.addProjectComponent = _a.addProjectComponent, exports.updateProjectComponent = _a.updateProjectComponent, exports.loadingRunner = _a.loadingRunner, exports.setLoadedRunner = _a.setLoadedRunner, exports.resetRunner = _a.resetRunner, exports.codeRunHandled = _a.codeRunHandled, exports.expireJustLoaded = _a.expireJustLoaded, exports.closeFile = _a.closeFile, exports.openFile = _a.openFile, exports.setOpenedFiles = _a.setOpenedFiles, exports.addFilePanel = _a.addFilePanel, exports.setFocussedFileIndex = _a.setFocussedFileIndex, exports.setPage = _a.setPage, exports.setAutorunEnabled = _a.setAutorunEnabled, exports.setIsOutputOnly = _a.setIsOutputOnly, exports.setCascadeUpdate = _a.setCascadeUpdate, exports.setError = _a.setError, exports.setHasShownSavePrompt = _a.setHasShownSavePrompt, exports.setProject = _a.setProject, exports.updateProjectSnapshot = _a.updateProjectSnapshot, exports.updateProjectCommits = _a.updateProjectCommits, exports.updateProjectIdentifier = _a.updateProjectIdentifier, exports.setReadOnly = _a.setReadOnly, exports.setLoading = _a.setLoading, exports.stopCodeRun = _a.stopCodeRun, exports.stopDraw = _a.stopDraw, exports.triggerCodeRun = _a.triggerCodeRun, exports.triggerDraw = _a.triggerDraw, exports.setSaveTriggered = _a.setSaveTriggered, exports.setProjectNameDraft = _a.setProjectNameDraft, exports.updateProjectName = _a.updateProjectName, exports.showErrorModal = _a.showErrorModal, exports.closeErrorModal = _a.closeErrorModal, exports.showModal = _a.showModal, exports.closeModal = _a.closeModal, exports.showSidebar = _a.showSidebar, exports.hideSidebar = _a.hideSidebar, exports.disableTheming = _a.disableTheming, exports.setSaving = _a.setSaving, exports.applyComponentsPatch = _a.applyComponentsPatch, exports.setCodeVisibility = _a.setCodeVisibility, exports.setSharedStatus = _a.setSharedStatus, exports.setCommits = _a.setCommits, exports.setShareLinks = _a.setShareLinks, exports.setCommitIdLoadTriggered = _a.setCommitIdLoadTriggered, exports.setIsLoadingCommit = _a.setIsLoadingCommit, exports.setRemixTriggered = _a.setRemixTriggered, exports.setShouldBypassCachedDraft = _a.setShouldBypassCachedDraft, exports.revertProject = _a.revertProject;
376020
+ _a = exports.EditorSlice.actions, exports.resetState = _a.resetState, exports.addProjectComponent = _a.addProjectComponent, exports.updateProjectComponent = _a.updateProjectComponent, exports.loadingRunner = _a.loadingRunner, exports.setLoadedRunner = _a.setLoadedRunner, exports.resetRunner = _a.resetRunner, exports.codeRunHandled = _a.codeRunHandled, exports.expireJustLoaded = _a.expireJustLoaded, exports.closeFile = _a.closeFile, exports.openFile = _a.openFile, exports.setOpenedFiles = _a.setOpenedFiles, exports.addFilePanel = _a.addFilePanel, exports.setFocussedFileIndex = _a.setFocussedFileIndex, exports.setPage = _a.setPage, exports.setAutorunEnabled = _a.setAutorunEnabled, exports.setIsOutputOnly = _a.setIsOutputOnly, exports.setCascadeUpdate = _a.setCascadeUpdate, exports.setError = _a.setError, exports.setHasShownSavePrompt = _a.setHasShownSavePrompt, exports.setProject = _a.setProject, exports.updateProjectSnapshot = _a.updateProjectSnapshot, exports.updateProjectCommits = _a.updateProjectCommits, exports.updateProjectIdentifier = _a.updateProjectIdentifier, exports.setReadOnly = _a.setReadOnly, exports.setLoading = _a.setLoading, exports.stopCodeRun = _a.stopCodeRun, exports.stopDraw = _a.stopDraw, exports.triggerCodeRun = _a.triggerCodeRun, exports.triggerDraw = _a.triggerDraw, exports.setSaveTriggered = _a.setSaveTriggered, exports.setProjectNameDraft = _a.setProjectNameDraft, exports.updateProjectName = _a.updateProjectName, exports.showErrorModal = _a.showErrorModal, exports.closeErrorModal = _a.closeErrorModal, exports.showModal = _a.showModal, exports.closeModal = _a.closeModal, exports.showSidebar = _a.showSidebar, exports.hideSidebar = _a.hideSidebar, exports.disableTheming = _a.disableTheming, exports.setSaving = _a.setSaving, exports.applyComponentsPatch = _a.applyComponentsPatch, exports.setCodeVisibility = _a.setCodeVisibility, exports.setSharedStatus = _a.setSharedStatus, exports.setCommits = _a.setCommits, exports.setShareLinks = _a.setShareLinks, exports.setCommitIdLoadTriggered = _a.setCommitIdLoadTriggered, exports.setIsLoadingCommit = _a.setIsLoadingCommit, exports.setRemixTriggered = _a.setRemixTriggered, exports.setShouldBypassCachedDraft = _a.setShouldBypassCachedDraft, exports.revertProject = _a.revertProject, exports.setCurriculumDisplayMode = _a.setCurriculumDisplayMode;
375584
376021
  exports.queueBinaryWrites = (0, toolkit_1.createAction)("editor/queueBinaryWrites");
375585
376022
  exports["default"] = exports.EditorSlice.reducer;
375586
376023
 
@@ -375720,6 +376157,7 @@ const utils_1 = __webpack_require__(33321);
375720
376157
  const sendToast_1 = __webpack_require__(50068);
375721
376158
  const constants_1 = __webpack_require__(16287);
375722
376159
  const fileValidation_1 = __webpack_require__(59615);
376160
+ const macSystemFiles_1 = __webpack_require__(18213);
375723
376161
  const selectComponents = (state) => {
375724
376162
  return state.editor.project.components ?? [];
375725
376163
  };
@@ -375791,23 +376229,27 @@ exports.fsRemove = fsRemove;
375791
376229
  const fsImportFiles = ({ files, parentId }) => async (dispatch, getState) => {
375792
376230
  const state = getState();
375793
376231
  const components = selectComponents(state);
375794
- if (!(0, utils_1.validateProjectFilesCount)(components, files.length)) {
376232
+ // Ignore macOS system files (__MACOSX/, .DS_Store)
376233
+ const importableFiles = (0, macSystemFiles_1.filterOutMacSystemFiles)(files);
376234
+ if (importableFiles.length === 0)
376235
+ return;
376236
+ if (!(0, utils_1.validateProjectFilesCount)(components, importableFiles.length)) {
375795
376237
  (0, sendToast_1.showError)(filesCountErrorMessage);
375796
376238
  return;
375797
376239
  }
375798
- const { validFiles, importErrors } = (0, fileValidation_1.validateFiles)(files);
376240
+ const { validFiles, importErrors } = (0, fileValidation_1.validateFiles)(importableFiles);
375799
376241
  const hasDuplicates = hasCaseInsensitiveDuplicateNames(validFiles);
375800
376242
  for (const error of importErrors) {
375801
376243
  (0, sendToast_1.showError)(error.reason);
375802
376244
  }
375803
376245
  if (validFiles.length > 0) {
375804
- (0, sendToast_1.showSuccess)(`Selected files uploaded successfully. ${validFiles.length} of ${files.length} file(s) uploaded.`);
376246
+ (0, sendToast_1.showSuccess)(`Selected files uploaded successfully. ${validFiles.length} of ${importableFiles.length} file(s) uploaded.`);
375805
376247
  if (hasDuplicates) {
375806
376248
  (0, sendToast_1.showInfo)(DUPLICATE_UPLOAD_TOAST);
375807
376249
  }
375808
376250
  }
375809
376251
  else {
375810
- (0, sendToast_1.showError)(`Selected files couldn’t be uploaded. ${files.length} file(s) failed to upload.`);
376252
+ (0, sendToast_1.showError)(`Selected files couldn’t be uploaded. ${importableFiles.length} file(s) failed to upload.`);
375811
376253
  return;
375812
376254
  }
375813
376255
  const componentsMap = (0, utils_1.buildComponentsById)(components);
@@ -375824,24 +376266,28 @@ const fsImportFolder = ({ files, parentId }) => async (dispatch, getState) => {
375824
376266
  return;
375825
376267
  const state = getState();
375826
376268
  const components = selectComponents(state);
375827
- if (!(0, utils_1.validateProjectFilesCount)(components, files.length)) {
376269
+ // Ignore macOS system files (__MACOSX/, .DS_Store)
376270
+ const importableFiles = (0, macSystemFiles_1.filterOutMacSystemFiles)(files);
376271
+ if (importableFiles.length === 0)
376272
+ return;
376273
+ if (!(0, utils_1.validateProjectFilesCount)(components, importableFiles.length)) {
375828
376274
  (0, sendToast_1.showError)(filesCountErrorMessage);
375829
376275
  return;
375830
376276
  }
375831
- const folderName = files[0]?.webkitRelativePath?.split("/")[0];
375832
- const { validFiles, importErrors } = (0, fileValidation_1.validateFiles)(files);
376277
+ const folderName = importableFiles[0]?.webkitRelativePath?.split("/")[0];
376278
+ const { validFiles, importErrors } = (0, fileValidation_1.validateFiles)(importableFiles);
375833
376279
  const hasDuplicates = hasCaseInsensitiveDuplicateNames(validFiles);
375834
376280
  for (const error of importErrors) {
375835
376281
  (0, sendToast_1.showError)(error.reason);
375836
376282
  }
375837
376283
  if (validFiles.length > 0) {
375838
- (0, sendToast_1.showSuccess)(`Folder '${folderName}' upload complete. ${validFiles.length} of ${files.length} file(s) uploaded successfully.`);
376284
+ (0, sendToast_1.showSuccess)(`Folder '${folderName}' upload complete. ${validFiles.length} of ${importableFiles.length} file(s) uploaded successfully.`);
375839
376285
  if (hasDuplicates) {
375840
376286
  (0, sendToast_1.showInfo)(DUPLICATE_UPLOAD_TOAST);
375841
376287
  }
375842
376288
  }
375843
376289
  else {
375844
- (0, sendToast_1.showError)(`'${folderName}' folder couldn't be uploaded. ${files.length} file(s) failed to upload.`);
376290
+ (0, sendToast_1.showError)(`'${folderName}' folder couldn't be uploaded. ${importableFiles.length} file(s) failed to upload.`);
375845
376291
  return;
375846
376292
  }
375847
376293
  const componentsMap = (0, utils_1.buildComponentsById)(components);
@@ -375985,7 +376431,7 @@ exports.useAppSelector = react_redux_1.useSelector;
375985
376431
 
375986
376432
 
375987
376433
  Object.defineProperty(exports, "__esModule", ({ value: true }));
375988
- exports.LoadingState = exports.SavingState = exports.ModalType = void 0;
376434
+ exports.CurriculumDisplayMode = exports.LoadingState = exports.SavingState = exports.ModalType = void 0;
375989
376435
  var ModalType;
375990
376436
  (function (ModalType) {
375991
376437
  ModalType["SHARE_PROJECT"] = "shareProject";
@@ -376010,6 +376456,11 @@ var LoadingState;
376010
376456
  LoadingState["SUCCESS"] = "success";
376011
376457
  LoadingState["FAILED"] = "failed";
376012
376458
  })(LoadingState || (exports.LoadingState = LoadingState = {}));
376459
+ var CurriculumDisplayMode;
376460
+ (function (CurriculumDisplayMode) {
376461
+ CurriculumDisplayMode["HIDDEN"] = "hidden";
376462
+ CurriculumDisplayMode["NORMAL"] = "normal";
376463
+ })(CurriculumDisplayMode || (exports.CurriculumDisplayMode = CurriculumDisplayMode = {}));
376013
376464
 
376014
376465
 
376015
376466
  /***/ }),
@@ -376568,20 +377019,39 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
376568
377019
  };
376569
377020
  Object.defineProperty(exports, "__esModule", ({ value: true }));
376570
377021
  exports.createProjectArchive = createProjectArchive;
376571
- const jszip_1 = __importDefault(__webpack_require__(71710));
376572
377022
  const ProjectTypes_1 = __webpack_require__(27130);
377023
+ const jszip_1 = __importDefault(__webpack_require__(71710));
376573
377024
  const binaryStore_1 = __webpack_require__(5060);
376574
- async function createProjectArchive(project) {
377025
+ const projectPath_1 = __webpack_require__(95506);
377026
+ async function createProjectArchive(project, folder) {
376575
377027
  let unzipSize = 0;
376576
377028
  const zip = new jszip_1.default();
376577
377029
  await Promise.all(project.components.map(async (component) => {
376578
377030
  if (component.type !== ProjectTypes_1.ProjectComponentType.FILE)
376579
377031
  return;
376580
377032
  const file = component;
376581
- // remove leading slash for windows compatibility
376582
- const safePath = file.path.startsWith("/")
376583
- ? file.path.slice(1)
376584
- : file.path;
377033
+ // Normalize once; we decide between "everything" and "scope to a
377034
+ // subfolder" by inspecting the normalized folder path.
377035
+ const normalizedFile = (0, projectPath_1.normalizePath)(file.path);
377036
+ const normalizedFolder = folder ? (0, projectPath_1.normalizePath)(folder.path) : null;
377037
+ let safePath;
377038
+ if (normalizedFolder && normalizedFolder !== "/") {
377039
+ // Scoped to a non-root subfolder: include only files within it,
377040
+ // with paths relative to that folder. `stripTopFolder` returns null
377041
+ // when the file is outside the folder and "" when the file path
377042
+ // equals the folder path (skip both).
377043
+ const relative = (0, projectPath_1.stripTopFolder)(normalizedFile, normalizedFolder);
377044
+ if (relative === null || relative === "")
377045
+ return;
377046
+ safePath = relative;
377047
+ }
377048
+ else {
377049
+ // Whole project (no folder, or the root folder "/"). Emit
377050
+ // Windows-friendly entries without a leading "/".
377051
+ if (normalizedFile === "/")
377052
+ return;
377053
+ safePath = normalizedFile.slice(1);
377054
+ }
376585
377055
  const blob = (await binaryStore_1.binaryStore.get(file.id)) ??
376586
377056
  new Blob([file.content], { type: file.mimeType || "text/plain" });
376587
377057
  unzipSize += blob.size;
@@ -376710,57 +377180,6 @@ const downloadProjectFile = async (file, fileName) => {
376710
377180
  exports.downloadProjectFile = downloadProjectFile;
376711
377181
 
376712
377182
 
376713
- /***/ }),
376714
-
376715
- /***/ 31615:
376716
- /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
376717
-
376718
-
376719
- Object.defineProperty(exports, "__esModule", ({ value: true }));
376720
- exports.matchingRegexes = exports.allowedInternalLinks = exports.allowedExternalLinks = exports.useExternalLinkState = void 0;
376721
- const react_1 = __webpack_require__(51649);
376722
- const react_redux_1 = __webpack_require__(14062);
376723
- const EditorSlice_1 = __webpack_require__(68512);
376724
- const Errors_1 = __webpack_require__(20339);
376725
- const domain = "https://rpf.io/";
376726
- const host = window?.location?.origin || "http://localhost:3011";
376727
- const rpfDomain = new RegExp(`^${domain}`);
376728
- const hostDomain = new RegExp(`^${host}`);
376729
- const allowedInternalLinks = [new RegExp(`^#[a-zA-Z0-9]+`)];
376730
- exports.allowedInternalLinks = allowedInternalLinks;
376731
- const allowedExternalLinks = [rpfDomain, hostDomain];
376732
- exports.allowedExternalLinks = allowedExternalLinks;
376733
- const useExternalLinkState = (showModal) => {
376734
- const dispatch = (0, react_redux_1.useDispatch)();
376735
- const [externalLink, setExternalLink] = (0, react_1.useState)(null);
376736
- const handleAllowedExternalLink = (linkTo) => {
376737
- setExternalLink(linkTo);
376738
- dispatch((0, EditorSlice_1.triggerCodeRun)());
376739
- };
376740
- const handleRegularExternalLink = (linkTo) => {
376741
- setExternalLink(null);
376742
- dispatch((0, EditorSlice_1.setPage)(linkTo));
376743
- dispatch((0, EditorSlice_1.triggerCodeRun)());
376744
- };
376745
- const handleExternalLinkError = () => {
376746
- dispatch((0, EditorSlice_1.setError)({ type: Errors_1.ErrorType.EXTERNAL_LINK }));
376747
- showModal();
376748
- };
376749
- return {
376750
- externalLink,
376751
- setExternalLink,
376752
- handleAllowedExternalLink,
376753
- handleRegularExternalLink,
376754
- handleExternalLinkError,
376755
- };
376756
- };
376757
- exports.useExternalLinkState = useExternalLinkState;
376758
- const matchingRegexes = (regexArray, testString) => {
376759
- return regexArray.some((reg) => reg.test(testString));
376760
- };
376761
- exports.matchingRegexes = matchingRegexes;
376762
-
376763
-
376764
377183
  /***/ }),
376765
377184
 
376766
377185
  /***/ 65404:
@@ -376778,6 +377197,7 @@ const ProjectTypes_1 = __webpack_require__(27130);
376778
377197
  const projectHelpers_1 = __webpack_require__(2610);
376779
377198
  const binaryStore_1 = __webpack_require__(5060);
376780
377199
  const projectBinaryIndex_1 = __webpack_require__(3044);
377200
+ const macSystemFiles_1 = __webpack_require__(18213);
376781
377201
  function normPath(p) {
376782
377202
  let s = (p || "").replace(/\\/g, "/").replace(/^\/+/, "");
376783
377203
  // remove extra folder in zipball zip
@@ -376804,7 +377224,7 @@ function pushChild(parent, childId) {
376804
377224
  parent.children.push(childId);
376805
377225
  }
376806
377226
  function getCommonRootFolder(entries) {
376807
- const fileEntries = entries.filter((entry) => !entry.dir);
377227
+ const fileEntries = entries.filter((entry) => !entry.dir && !(0, macSystemFiles_1.isMacSystemFilePath)(entry.name));
376808
377228
  const [firstFileEntry] = fileEntries;
376809
377229
  if (!firstFileEntry)
376810
377230
  return "";
@@ -376839,10 +377259,13 @@ async function loadProjectFromZip(opts) {
376839
377259
  for (const entry of entries) {
376840
377260
  if (entry.dir)
376841
377261
  continue;
376842
- const normalizedEntryName = normPath(entry.name);
376843
- const raw = rootPath
376844
- ? normPath(normalizedEntryName.slice(rootPath.length))
376845
- : normalizedEntryName;
377262
+ // skip mac system files (__MACOSX/, .DS_Store)
377263
+ if ((0, macSystemFiles_1.isMacSystemFilePath)(entry.name))
377264
+ continue;
377265
+ const normalized = normPath(entry.name);
377266
+ const raw = rootPath && normalized.startsWith(rootPath)
377267
+ ? normalized.slice(rootPath.length)
377268
+ : normalized;
376846
377269
  if (!raw)
376847
377270
  continue;
376848
377271
  const parts = raw.split("/").filter(Boolean);
@@ -376902,6 +377325,34 @@ async function loadProjectFromZip(opts) {
376902
377325
  }
376903
377326
 
376904
377327
 
377328
+ /***/ }),
377329
+
377330
+ /***/ 18213:
377331
+ /***/ ((__unused_webpack_module, exports) => {
377332
+
377333
+
377334
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
377335
+ exports.filterOutMacSystemFiles = exports.isMacSystemFilePath = void 0;
377336
+ const normalizeSlashes = (p) => (p || "").replace(/\\/g, "/").trim();
377337
+ const isMacSystemFilePath = (rawEntryPath) => {
377338
+ const path = normalizeSlashes(rawEntryPath);
377339
+ if (!path)
377340
+ return false;
377341
+ const fileName = path.split("/").filter(Boolean).pop() || "";
377342
+ // skip mac system files
377343
+ if (path.startsWith("__MACOSX/") || path.includes("/__MACOSX/"))
377344
+ return true;
377345
+ if (fileName === ".DS_Store")
377346
+ return true;
377347
+ if (fileName.startsWith("._"))
377348
+ return true;
377349
+ return false;
377350
+ };
377351
+ exports.isMacSystemFilePath = isMacSystemFilePath;
377352
+ const filterOutMacSystemFiles = (files) => Array.from(files).filter((file) => !(0, exports.isMacSystemFilePath)(file.webkitRelativePath?.trim() || file.name));
377353
+ exports.filterOutMacSystemFiles = filterOutMacSystemFiles;
377354
+
377355
+
376905
377356
  /***/ }),
376906
377357
 
376907
377358
  /***/ 3044: