@learnpack/learnpack 5.0.291 → 5.0.292

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.
Files changed (33) hide show
  1. package/lib/commands/init.js +42 -42
  2. package/lib/commands/serve.js +492 -33
  3. package/lib/creatorDist/assets/{index-C39zeF3W.css → index-CacFtcN8.css} +31 -0
  4. package/lib/creatorDist/assets/{index-wLKEQIG6.js → index-DOEfLGDQ.js} +1424 -1372
  5. package/lib/creatorDist/index.html +2 -2
  6. package/lib/models/creator.d.ts +4 -0
  7. package/lib/utils/api.d.ts +1 -1
  8. package/lib/utils/api.js +2 -1
  9. package/lib/utils/rigoActions.d.ts +14 -0
  10. package/lib/utils/rigoActions.js +43 -1
  11. package/package.json +1 -1
  12. package/src/commands/init.ts +655 -650
  13. package/src/commands/serve.ts +794 -48
  14. package/src/creator/src/App.tsx +12 -12
  15. package/src/creator/src/components/FileUploader.tsx +2 -68
  16. package/src/creator/src/components/LessonItem.tsx +47 -8
  17. package/src/creator/src/components/Login.tsx +0 -6
  18. package/src/creator/src/components/syllabus/ContentIndex.tsx +11 -0
  19. package/src/creator/src/components/syllabus/SyllabusEditor.tsx +6 -1
  20. package/src/creator/src/index.css +227 -217
  21. package/src/creator/src/locales/en.json +2 -2
  22. package/src/creator/src/locales/es.json +2 -2
  23. package/src/creator/src/utils/lib.ts +470 -468
  24. package/src/creator/src/utils/rigo.ts +85 -85
  25. package/src/creatorDist/assets/{index-C39zeF3W.css → index-CacFtcN8.css} +31 -0
  26. package/src/creatorDist/assets/{index-wLKEQIG6.js → index-DOEfLGDQ.js} +1424 -1372
  27. package/src/creatorDist/index.html +2 -2
  28. package/src/models/creator.ts +2 -0
  29. package/src/ui/_app/app.css +1 -1
  30. package/src/ui/_app/app.js +363 -361
  31. package/src/ui/app.tar.gz +0 -0
  32. package/src/utils/api.ts +2 -1
  33. package/src/utils/rigoActions.ts +73 -0
@@ -128,7 +128,7 @@ function App() {
128
128
  })
129
129
  }
130
130
  if (duration && !isNaN(parseInt(duration))) {
131
- if (["30", "60", "120"].includes(duration)) {
131
+ if (["15", "30", "60"].includes(duration)) {
132
132
  const durationInt = parseInt(duration)
133
133
  console.log("duration", durationInt)
134
134
  setFormState({
@@ -307,6 +307,17 @@ function App() {
307
307
  required: true,
308
308
  content: (
309
309
  <div className="flex flex-col md:flex-row gap-2">
310
+ <SelectableCard
311
+ title={t("stepWizard.durationCard.15")}
312
+ // subtitle="This is a tutorial that will take 15 minutes to complete"
313
+ onClick={() => {
314
+ setFormState({
315
+ duration: 15,
316
+ currentStep: "verifyHuman",
317
+ })
318
+ }}
319
+ selected={formState.duration === 15}
320
+ />
310
321
  <SelectableCard
311
322
  title={t("stepWizard.durationCard.30")}
312
323
  // subtitle="This is a tutorial that will take 30 minutes to complete"
@@ -329,17 +340,6 @@ function App() {
329
340
  }}
330
341
  selected={formState.duration === 60}
331
342
  />
332
- <SelectableCard
333
- title={t("stepWizard.durationCard.120")}
334
- // subtitle="This is a tutorial that will take 2 hours to complete"
335
- onClick={() => {
336
- setFormState({
337
- duration: 120,
338
- currentStep: "verifyHuman",
339
- })
340
- }}
341
- selected={formState.duration === 120}
342
- />
343
343
  </div>
344
344
  ),
345
345
  },
@@ -8,67 +8,6 @@ import { DEV_MODE, RIGOBOT_HOST } from "../utils/constants"
8
8
  import axios from "axios"
9
9
  import { useTranslation } from "react-i18next"
10
10
 
11
- // `
12
- // app.post("/read-document", upload.single("file"), async (req, res) => {
13
- // console.log("READING A DOCUMENT")
14
- // const publicToken = req.header("x-public-token")
15
- // if (!publicToken) {
16
- // return res.status(400).json({ error: "Public token is required" })
17
- // }
18
-
19
- // try {
20
- // // eslint-disable-next-line
21
- // // @ts-ignore
22
- // if (!req.file) {
23
- // return res.status(400).json({ error: "Missing file" })
24
- // }
25
-
26
- // console.log("PUBLIC TOKEN", publicToken)
27
-
28
- // const resultId = `document-read-${Date.now()}-${Math.floor(
29
- // Math.random() * 1e6
30
- // )}`
31
-
32
- // const webhookUrl = `${deploymentURL}/notifications/${resultId}`
33
- // console.log("WEBHOOK URL", webhookUrl)
34
- // // Construir form-data para enviar al servidor proxy
35
- // const formData = new FormData()
36
- // // eslint-disable-next-line
37
- // // @ts-ignore
38
- // formData.append("file", req.file.buffer, {
39
- // // eslint-disable-next-line
40
- // // @ts-ignore
41
- // filename: req.file.originalname,
42
- // // eslint-disable-next-line
43
- // // @ts-ignore
44
- // contentType: req.file.mimetype,
45
- // })
46
- // formData.append("webhook_callback_url", webhookUrl)
47
-
48
- // try {
49
- // const response = await axios.post(
50
- // `${RIGOBOT_HOST}/v1/learnpack/public/tools/read-document`,
51
- // formData,
52
- // {
53
- // headers: {
54
- // ...formData.getHeaders(),
55
- // Authorization: `Token ${publicToken.trim()}`,
56
- // },
57
- // }
58
- // )
59
- // } catch (error) {
60
- // console.error("❌ Error in /read-document:", error)
61
- // return res.status(500).json({ error: (error as Error).message })
62
- // }
63
-
64
- // return res.json({ notificationId: resultId, status: "PROCESSING" })
65
- // } catch (error) {
66
- // console.error("❌ Error in /read-document:", error)
67
- // return res.status(500).json({ error: (error as Error).message })
68
- // }
69
- // })
70
- // `
71
-
72
11
  const socketClient = new CreatorSocket("")
73
12
 
74
13
  const allowedTypes = [
@@ -155,12 +94,6 @@ const UploadedFileCard = ({ idx, file }: { idx: number; file: ParsedFile }) => {
155
94
  }
156
95
  onClick={
157
96
  () => setUploadedFiles(uploadedFiles.filter((_, i) => i !== idx))
158
- // Set the file as error
159
- // setUploadedFiles(
160
- // uploadedFiles.map((f, i) =>
161
- // i === idx ? { ...f, status: "ERROR" } : f
162
- // )
163
- // )
164
97
  }
165
98
  >
166
99
  {SVGS.trash}
@@ -202,7 +135,8 @@ const FileUploader: React.FC<FileUploaderProps> = ({
202
135
 
203
136
  const webhookUrl = `${
204
137
  DEV_MODE
205
- ? "https://9cw5zmww-3000.use2.devtunnels.ms"
138
+ ? "https://1gm40gnb-3000.use2.devtunnels.ms"
139
+ // : "https://1gm40gnb-3000.use2.devtunnels.ms"
206
140
  : window.location.origin
207
141
  }/notifications/${resultId}`
208
142
  formData.append("webhook_callback_url", webhookUrl)
@@ -13,6 +13,7 @@ export interface Lesson {
13
13
  type: "READ" | "CODE" | "QUIZ"
14
14
  description: string
15
15
  duration?: number
16
+ locked?: boolean
16
17
  }
17
18
 
18
19
  const typeToEmoji: Record<string, string> = {
@@ -26,6 +27,7 @@ interface LessonItemProps {
26
27
  isNew: boolean
27
28
  onChange: (uid: string, newTitle: string) => void
28
29
  onRemove: () => void
30
+ onToggleLock?: (uid: string) => void
29
31
  }
30
32
 
31
33
  function cleanFloatString(input: string): string {
@@ -47,6 +49,7 @@ export const LessonItem: React.FC<LessonItemProps> = ({
47
49
  lesson,
48
50
  onChange,
49
51
  onRemove,
52
+ onToggleLock,
50
53
  isNew,
51
54
  }) => {
52
55
  const mode = useStore((state) => state.mode)
@@ -57,13 +60,20 @@ export const LessonItem: React.FC<LessonItemProps> = ({
57
60
  <div
58
61
  className={`flex items-center space-x-2 relative rounded-md p-3 ${
59
62
  isNew ? "border border-learnpack-blue appear" : "border border-gray-200"
60
- } ${hasDecimalPart(lesson.id.toString() || "0") ? "ml-6" : ""}`}
63
+ } ${hasDecimalPart(lesson.id.toString() || "0") ? "ml-6" : ""} ${
64
+ lesson.locked ? "bg-yellow-50 border-yellow-300" : ""
65
+ }`}
61
66
  >
62
- {isNew && <span className="red-ball"></span>}
67
+ {isNew && <span className="yellow-ball"></span>}
63
68
 
64
69
  {mode === "teacher" && (
65
70
  <>
66
71
  <span className="index-circle">{cleanFloatString(lesson.id)}</span>
72
+ {lesson.locked && (
73
+ <span className="text-yellow-600 text-sm" title="Lesson is locked">
74
+ 🔒
75
+ </span>
76
+ )}
67
77
  </>
68
78
  )}
69
79
 
@@ -92,20 +102,49 @@ export const LessonItem: React.FC<LessonItemProps> = ({
92
102
 
93
103
  {mode === "teacher" && (
94
104
  <>
105
+ <button
106
+ onClick={() => onToggleLock?.(lesson.uid)}
107
+ className={`cursor-pointer ${
108
+ lesson.locked
109
+ ? "text-yellow-600 hover:text-yellow-700"
110
+ : "text-gray-400 hover:text-yellow-600"
111
+ }`}
112
+ title={lesson.locked ? "Unlock lesson" : "Lock lesson"}
113
+ >
114
+ 🔒
115
+ </button>
95
116
  <button
96
117
  onClick={() => {
97
- setIsEditing(!isEditing)
98
- if (inputRef.current) {
99
- onChange(lesson.uid, inputRef.current.value)
118
+ if (!lesson.locked) {
119
+ setIsEditing(!isEditing)
120
+ if (inputRef.current) {
121
+ onChange(lesson.uid, inputRef.current.value)
122
+ }
100
123
  }
101
124
  }}
102
- className="text-gray-500 hover:text-blue-500 cursor-pointer "
125
+ className={`${
126
+ lesson.locked
127
+ ? "text-gray-300 cursor-not-allowed"
128
+ : "text-gray-500 hover:text-blue-500 cursor-pointer"
129
+ }`}
130
+ disabled={lesson.locked}
131
+ title={lesson.locked ? "Cannot edit locked lesson" : "Edit lesson"}
103
132
  >
104
133
  {SVGS.pen}
105
134
  </button>
106
135
  <button
107
- onClick={() => onRemove()}
108
- className="text-red-500 hover:text-red-700 cursor-pointer"
136
+ onClick={() => {
137
+ if (!lesson.locked) {
138
+ onRemove()
139
+ }
140
+ }}
141
+ className={`${
142
+ lesson.locked
143
+ ? "text-gray-300 cursor-not-allowed"
144
+ : "text-red-500 hover:text-red-700 cursor-pointer"
145
+ }`}
146
+ disabled={lesson.locked}
147
+ title={lesson.locked ? "Cannot delete locked lesson" : "Delete lesson"}
109
148
  >
110
149
  {SVGS.trash}
111
150
  </button>
@@ -241,12 +241,6 @@ export default function Login({ onFinish }: { onFinish: () => void }) {
241
241
  <p className="text-gray-700 m-0">
242
242
  {t("login.youDontHaveAnAccount")}
243
243
  </p>
244
- {/* <button
245
- className="text-blue-600 font-medium cursor-pointer"
246
- onClick={() => setShowSignup(true)}
247
- >
248
- Register here.
249
- </button> */}
250
244
  <a
251
245
  href={`https://www.learnpack.co/register?callback=${encodeURIComponent(window.location.href)}`}
252
246
  target="_blank"
@@ -184,6 +184,15 @@ export const ContentIndex = ({
184
184
  })
185
185
  }
186
186
 
187
+ const handleToggleLock = (uid: string) => {
188
+ push({
189
+ ...syllabus,
190
+ lessons: syllabus.lessons.map((lesson) =>
191
+ lesson.uid === uid ? { ...lesson, locked: !lesson.locked } : lesson
192
+ ),
193
+ })
194
+ }
195
+
187
196
  const addLessonAfter = (index: number, id: string) => {
188
197
  const newLesson: Lesson = {
189
198
  id: (parseFloat(id) + 0.1).toFixed(1),
@@ -192,6 +201,7 @@ export const ContentIndex = ({
192
201
  type: "READ",
193
202
  duration: 2,
194
203
  description: "Hello World",
204
+ locked: false,
195
205
  }
196
206
  const updated = [...syllabus.lessons]
197
207
  updated.splice(index + 1, 0, newLesson)
@@ -258,6 +268,7 @@ export const ContentIndex = ({
258
268
  lesson={lesson}
259
269
  onChange={handleChange}
260
270
  onRemove={() => handleRemove(lesson)}
271
+ onToggleLock={handleToggleLock}
261
272
  isNew={Boolean(
262
273
  previousSyllabus &&
263
274
  previousSyllabus &&
@@ -163,6 +163,8 @@ const SyllabusEditor: React.FC = () => {
163
163
  { type: "assistant", content: "" },
164
164
  ])
165
165
 
166
+ const lessonsText = JSON.stringify(syllabus.lessons)
167
+
166
168
  const res = await publicInteractiveCreation(
167
169
  {
168
170
  courseInfo:
@@ -170,7 +172,10 @@ const SyllabusEditor: React.FC = () => {
170
172
  `\nThe following technologies are available, choose up to 3 from the following list: <techs>${technologies
171
173
  .filter((t) => t.lang === syllabus.courseInfo.language)
172
174
  .map((t) => t.slug)
173
- .join(", ")}</techs>`,
175
+ .join(", ")}</techs> \nThe following lessons are already in the syllabus: ${lessonsText}
176
+
177
+ You must NEVER delete lessons that are marked as locked.
178
+ `,
174
179
  prevInteractions:
175
180
  messages
176
181
  .map((message) => `${message.type}: ${message.content}`)