feat(lesson-preparation): add readonly view, anchor node selector, and type guards
- Add lesson-plan-readonly-view for viewing published plans - Add anchor-node-selector and textbook-segments for canvas anchor positioning - Add i18n-errors and type-guards lib utilities - Add lesson-plan-provider-setup for provider initialization - Update actions, data-access (knowledge, versions, main), publish-service - Update blocks (blackboard, exercise, homework, import, key-point, objective, reflection) - Update editor, node-editor, node-edit-panel, pickers, and providers
This commit is contained in:
@@ -4,6 +4,7 @@ import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Tag } from "lucide-react";
|
||||
import type { BlackboardBlockData } from "../../types";
|
||||
import { isBlackboardLayout } from "../../lib/type-guards";
|
||||
import { KnowledgePointPicker } from "../knowledge-point-picker";
|
||||
|
||||
interface Props {
|
||||
@@ -30,12 +31,12 @@ export function BlackboardBlock({ data, textbookId, chapterId, onUpdate }: Props
|
||||
</label>
|
||||
<select
|
||||
value={data.layout}
|
||||
onChange={(e) =>
|
||||
onUpdate({
|
||||
...data,
|
||||
layout: e.target.value as BlackboardBlockData["layout"],
|
||||
})
|
||||
}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (isBlackboardLayout(value)) {
|
||||
onUpdate({ ...data, layout: value });
|
||||
}
|
||||
}}
|
||||
className="w-full text-sm border border-outline-variant rounded px-2 py-1 bg-surface"
|
||||
>
|
||||
{LAYOUTS.map((l) => (
|
||||
|
||||
@@ -12,8 +12,8 @@ import { Plus, Trash2 } from "lucide-react";
|
||||
import type {
|
||||
ExerciseBlockData,
|
||||
ExerciseItem,
|
||||
ExercisePurpose,
|
||||
} from "../../types";
|
||||
import { isExercisePurpose } from "../../lib/type-guards";
|
||||
|
||||
interface Props {
|
||||
blockId: string;
|
||||
@@ -59,9 +59,12 @@ export function ExerciseBlock({ blockId, data, classes, textbookId, chapterId }:
|
||||
<select
|
||||
id={`exercise-purpose-${blockId}`}
|
||||
value={data.purpose}
|
||||
onChange={(e) =>
|
||||
update({ purpose: e.target.value as ExercisePurpose })
|
||||
}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (isExercisePurpose(value)) {
|
||||
update({ purpose: value });
|
||||
}
|
||||
}}
|
||||
className="border rounded px-2 py-1 text-sm"
|
||||
>
|
||||
<option value="class_practice">{t("exercise.purpose.class_practice")}</option>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Plus, Trash2 } from "lucide-react";
|
||||
import type { HomeworkAssignment, HomeworkBlockData } from "../../types";
|
||||
import { isHomeworkType } from "../../lib/type-guards";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
|
||||
interface Props {
|
||||
@@ -45,11 +46,12 @@ export function HomeworkBlock({ data, onUpdate }: Props) {
|
||||
<div key={idx} className="flex items-start gap-2">
|
||||
<select
|
||||
value={item.type}
|
||||
onChange={(e) =>
|
||||
updateItem(idx, {
|
||||
type: e.target.value as HomeworkAssignment["type"],
|
||||
})
|
||||
}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (isHomeworkType(value)) {
|
||||
updateItem(idx, { type: value });
|
||||
}
|
||||
}}
|
||||
className="text-xs border border-outline-variant rounded px-1 py-1 bg-surface"
|
||||
>
|
||||
{TYPES.map((tp) => (
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useTranslations } from "next-intl";
|
||||
import type { ImportBlockData } from "../../types";
|
||||
import { isImportMethod } from "../../lib/type-guards";
|
||||
|
||||
interface Props {
|
||||
data: ImportBlockData;
|
||||
@@ -24,9 +25,12 @@ export function ImportBlock({ data, onUpdate }: Props) {
|
||||
</label>
|
||||
<select
|
||||
value={data.method}
|
||||
onChange={(e) =>
|
||||
onUpdate({ ...data, method: e.target.value as ImportBlockData["method"] })
|
||||
}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (isImportMethod(value)) {
|
||||
onUpdate({ ...data, method: value });
|
||||
}
|
||||
}}
|
||||
className="w-full text-sm border border-outline-variant rounded px-2 py-1 bg-surface"
|
||||
>
|
||||
{METHODS.map((m) => (
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Plus, Trash2 } from "lucide-react";
|
||||
import type { KeyPointBlockData, KeyPointItem } from "../../types";
|
||||
import { isKeyPointType } from "../../lib/type-guards";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
|
||||
interface Props {
|
||||
@@ -45,9 +46,12 @@ export function KeyPointBlock({ data, onUpdate }: Props) {
|
||||
<div key={idx} className="flex items-start gap-2">
|
||||
<select
|
||||
value={item.type}
|
||||
onChange={(e) =>
|
||||
updateItem(idx, { type: e.target.value as KeyPointItem["type"] })
|
||||
}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (isKeyPointType(value)) {
|
||||
updateItem(idx, { type: value });
|
||||
}
|
||||
}}
|
||||
className="text-xs border border-outline-variant rounded px-1 py-1 bg-surface"
|
||||
>
|
||||
{TYPES.map((tp) => (
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Plus, Trash2 } from "lucide-react";
|
||||
import type { ObjectiveBlockData, ObjectiveItem } from "../../types";
|
||||
import { isObjectiveDimension } from "../../lib/type-guards";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
|
||||
interface Props {
|
||||
@@ -48,11 +49,12 @@ export function ObjectiveBlock({ data, onUpdate }: Props) {
|
||||
<div key={idx} className="flex items-start gap-2">
|
||||
<select
|
||||
value={item.dimension}
|
||||
onChange={(e) =>
|
||||
updateItem(idx, {
|
||||
dimension: e.target.value as ObjectiveItem["dimension"],
|
||||
})
|
||||
}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (isObjectiveDimension(value)) {
|
||||
updateItem(idx, { dimension: value });
|
||||
}
|
||||
}}
|
||||
className="text-xs border border-outline-variant rounded px-1 py-1 bg-surface"
|
||||
>
|
||||
{DIMENSIONS.map((d) => (
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Plus, Trash2 } from "lucide-react";
|
||||
import type { ReflectionBlockData, ReflectionItem } from "../../types";
|
||||
import { isReflectionAspect } from "../../lib/type-guards";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
|
||||
interface Props {
|
||||
@@ -45,11 +46,12 @@ export function ReflectionBlock({ data, onUpdate }: Props) {
|
||||
<div key={idx} className="flex items-start gap-2">
|
||||
<select
|
||||
value={item.aspect}
|
||||
onChange={(e) =>
|
||||
updateItem(idx, {
|
||||
aspect: e.target.value as ReflectionItem["aspect"],
|
||||
})
|
||||
}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (isReflectionAspect(value)) {
|
||||
updateItem(idx, { aspect: value });
|
||||
}
|
||||
}}
|
||||
className="text-xs border border-outline-variant rounded px-1 py-1 bg-surface"
|
||||
>
|
||||
{ASPECTS.map((a) => (
|
||||
|
||||
Reference in New Issue
Block a user