feat: add elements

This commit is contained in:
swve 2022-11-11 20:36:09 +01:00
parent 938cfcb4b3
commit 6f2cc5bdc6
23 changed files with 241 additions and 104 deletions

View file

@ -1,9 +1,51 @@
import React from "react";
import styled from "styled-components";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import Element, { ElementWrapper } from "./element";
import Link from "next/link";
import { motion } from "framer-motion";
import Element, { ElementWrapper } from "./Element";
function Chapter(props: any) {
return (
<Draggable key={props.info.list.chapter.id} draggableId={props.info.list.chapter.id} index={props.index}>
{(provided, snapshot) => (
<ChapterWrapper
{...provided.dragHandleProps}
{...provided.draggableProps}
ref={provided.innerRef}
// isDragging={snapshot.isDragging}
key={props.info.list.chapter.id}
>
<h3>
{props.info.list.chapter.name}{" "}
<button
onClick={() => {
props.openNewElementModal(props.info.list.chapter.id);
}}
>
Create Element
</button>
<button
onClick={() => {
props.deleteChapter(props.info.list.chapter.id);
}}
>
X
</button>
</h3>
<Droppable key={props.info.list.chapter.id} droppableId={props.info.list.chapter.id} type="element">
{(provided) => (
<ElementsList {...provided.droppableProps} ref={provided.innerRef}>
{props.info.list.elements.map((element: any, index: any) => (
<Element key={element.id} element={element} index={index}></Element>
))}
{provided.placeholder}
</ElementsList>
)}
</Droppable>
</ChapterWrapper>
)}
</Draggable>
);
}
const ChapterWrapper = styled.div`
margin-bottom: 5px;
@ -17,45 +59,6 @@ const ChapterWrapper = styled.div`
transition: all 0.2s ease;
`;
function Chapter(props: any) {
return (
<Draggable key={props.info.list.chapter.id} draggableId={props.info.list.chapter.id} index={props.index}>
{(provided, snapshot) => (
<ChapterWrapper
{...provided.dragHandleProps}
{...provided.draggableProps}
ref={provided.innerRef}
isDragging={snapshot.isDragging}
key={props.info.list.chapter.id}
>
<h3>
{props.info.list.chapter.name}{" "}
<button
onClick={() => {
props.deleteChapter(props.info.list.chapter.id);
}}
>
X
</button>
</h3>
<Droppable key={props.info.list.chapter.id} droppableId={props.info.list.chapter.id} type="element">
{(provided) => (
<ElementsList {...provided.droppableProps} ref={provided.innerRef}>
{props.info.list.elements.map((element: any, index: any) => (
<Element key={element.id} element={element} index={index}></Element>
))}
{provided.placeholder}
</ElementsList>
)}
</Droppable>
</ChapterWrapper>
)}
</Draggable>
);
}
const ElementsList = styled.div`
padding: 10px;
`;

View file

@ -7,7 +7,7 @@ function Element(props: any) {
<Draggable key={props.element.id} draggableId={props.element.id} index={props.index}>
{(provided) => (
<ElementWrapper key={props.element.id} {...provided.draggableProps} {...provided.dragHandleProps} ref={provided.innerRef}>
{props.element.content}
<p>{props.element.name} </p>
</ElementWrapper>
)}
</Draggable>

View file

@ -0,0 +1,39 @@
import React, { useState } from "react";
import Modal from "../Modal";
function NewElementModal({ closeModal, submitElement, chapterId }: any) {
const [elementName, setElementName] = useState("");
const [elementDescription, setElementDescription] = useState("");
const handleElementNameChange = (e: any) => {
setElementName(e.target.value);
};
const handleElementDescriptionChange = (e: any) => {
setElementDescription(e.target.value);
};
const handleSubmit = async (e: any) => {
e.preventDefault();
console.log({ elementName, elementDescription, chapterId });
submitElement({
name: elementName,
chapterId: chapterId,
type: "dynamic",
});
};
return (
<Modal>
<h1>
Add New Element <button onClick={closeModal}>X</button>
</h1>
<input type="text" onChange={handleElementNameChange} placeholder="Element Name" /> <br />
<input type="text" onChange={handleElementDescriptionChange} placeholder="Element Description" />
<br />
<button onClick={handleSubmit}>Add Element</button>
</Modal>
);
}
export default NewElementModal;

View file

@ -1,5 +1,4 @@
import React from "react";
import { Menu } from "./elements/menu";
import Link from 'next/link'
import styled from "styled-components";

View file

@ -1,10 +1,9 @@
import React from "react";
import Head from "next/head";
import { Header } from "./header";
import styled from "styled-components";
import AuthProvider from "../security/AuthProvider";
import { motion } from "framer-motion";
import { Menu } from "./elements/menu";
import { Menu } from "./elements/Menu";
const Layout = (props: any) => {
const variants = {

View file

@ -4,7 +4,7 @@ import styled from "styled-components";
import learnhouseBigIcon from "public/learnhouse_bigicon.png";
import Image from "next/image";
import Link from "next/link";
import { PreAlphaLabel } from "../components/ui/layout";
import { PreAlphaLabel } from "../components/ui/Layout";
const Home: NextPage = () => {
return (

View file

@ -1,8 +1,8 @@
import Router from "next/router";
import React from "react";
import { Header } from "../components/ui/header";
import Layout from "../components/ui/layout";
import { Title } from "../components/ui/styles/title";
import { Header } from "../components/ui/Header";
import Layout from "../components/ui/Layout";
import { Title } from "../components/ui/styles/Title";
import { loginAndGetToken } from "../services/auth/auth";
const Login = () => {

View file

@ -1,7 +1,7 @@
import { default as React, useEffect, useRef } from "react";
import Layout from "../../../../../../components/ui/layout";
import { Title } from "../../../../../../components/ui/styles/title";
import Layout from "../../../../../../components/ui/Layout";
import { Title } from "../../../../../../components/ui/styles/Title";
import dynamic from "next/dynamic";
import { AuthContext } from "../../../../../../components/security/AuthProvider";

View file

@ -1,27 +1,38 @@
import React from "react";
import { useState, useEffect } from "react";
import styled from "styled-components";
import { Header } from "../../../../../../components/ui/header";
import Layout from "../../../../../../components/ui/layout";
import { Title } from "../../../../../../components/ui/styles/title";
import { Header } from "../../../../../../components/ui/Header";
import Layout from "../../../../../../components/ui/Layout";
import { Title } from "../../../../../../components/ui/styles/Title";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import { initialData, initialData2 } from "../../../../../../components/drags/data";
import Chapter from "../../../../../../components/drags/chapter";
import { createChapter, deleteChapter, getCourseChaptersMetadata } from "../../../../../../services/chapters";
import Chapter from "../../../../../../components/drags/Chapter";
import { createChapter, deleteChapter, getCourseChaptersMetadata } from "../../../../../../services/courses/chapters";
import { useRouter } from "next/router";
import NewChapterModal from "../../../../../../components/modals/chapters/new";
import NewChapterModal from "../../../../../../components/modals/CourseEdit/NewChapter";
import NewElementModal from "../../../../../../components/modals/CourseEdit/NewElement";
import { createElement } from "../../../../../../services/courses/elements";
function CourseEdit() {
const router = useRouter();
// Initial Course State
const [data, setData] = useState(initialData2) as any;
// New Chapter Modal State
const [newChapterModal, setNewChapterModal] = useState(false) as any;
// New Element Modal State
const [newElementModal, setNewElementModal] = useState(false) as any;
const [newElementModalData, setNewElementModalData] = useState("") as any;
// Check window availability
const [winReady, setwinReady] = useState(false);
const { courseid } = router.query;
async function getCourseChapters() {
const courseChapters = await getCourseChaptersMetadata(courseid);
setData(courseChapters);
console.log( "courseChapters" , courseChapters);
console.log("courseChapters", courseChapters);
}
useEffect(() => {
@ -59,20 +70,44 @@ function CourseEdit() {
setNewChapterModal(false);
};
// Submit new element
const submitElement = async (element: any) => {
console.log("submitElement", element);
await createElement(element, element.chapterId);
getCourseChapters();
setNewElementModal(false);
};
const deleteChapterUI = async (chapterId: any) => {
console.log("deleteChapter", chapterId);
await deleteChapter(chapterId);
getCourseChapters();
};
const openNewElementModal = async (chapterId: any) => {
console.log("openNewElementModal", chapterId);
setNewElementModal(true);
setNewElementModalData(chapterId);
};
/*
Modals
*/
// Close new chapter modal
const closeModal = () => {
const closeNewChapterModal = () => {
setNewChapterModal(false);
};
const closeNewElementModal = () => {
setNewElementModal(false);
};
/*
Drag and drop functions
*/
const onDragEnd = (result: any) => {
const { destination, source, draggableId, type } = result;
console.log(result);
@ -175,21 +210,34 @@ function CourseEdit() {
<Layout>
<Header></Header>
<Title>
Edit Course Chapters <button onClick={()=> {setNewChapterModal(true)}}>+</button>
Edit Course Chapters{" "}
<button
onClick={() => {
setNewChapterModal(true);
}}
>
+
</button>
</Title>
{newChapterModal && <NewChapterModal closeModal={closeModal} submitChapter={submitChapter}></NewChapterModal>}
{newChapterModal && <NewChapterModal closeModal={closeNewChapterModal} submitChapter={submitChapter}></NewChapterModal>}
{newElementModal && <NewElementModal closeModal={closeNewElementModal} submitElement={submitElement} chapterId={newElementModalData}></NewElementModal>}
<br />
{winReady && (
{winReady && (
<ChapterlistWrapper>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable key="chapters" droppableId="chapters" type="chapter">
{(provided) => (
<div key={"chapters"} {...provided.droppableProps} ref={provided.innerRef}>
{getChapters().map((info: any, index: any) => (
<Chapter deleteChapter={deleteChapterUI} key={index} info={info} index={index}></Chapter>
))}
{provided.placeholder}
</div>
<>
<div key={"chapters"} {...provided.droppableProps} ref={provided.innerRef}>
{getChapters().map((info: any, index: any) => (
<>
<Chapter openNewElementModal={openNewElementModal} deleteChapter={deleteChapterUI} key={index} info={info} index={index}></Chapter>
</>
))}
{provided.placeholder}
</div>
</>
)}
</Droppable>
</DragDropContext>

View file

@ -1,9 +1,9 @@
import { useRouter } from "next/router";
import React from "react";
import styled from "styled-components";
import Layout from "../../../../../components/ui/layout";
import Layout from "../../../../../components/ui/Layout";
import { getAPIUrl, getBackendUrl } from "../../../../../services/config";
import { getCourse } from "../../../../../services/courses";
import { getCourse } from "../../../../../services/courses/courses";
import { getOrganizationContextInfo } from "../../../../../services/orgs";
const CourseIdPage = () => {

View file

@ -2,11 +2,11 @@ import Link from "next/link";
import { useRouter } from "next/router";
import React from "react";
import styled from "styled-components";
import { Header } from "../../../../components/ui/header";
import Layout from "../../../../components/ui/layout";
import { Title } from "../../../../components/ui/styles/title";
import { Header } from "../../../../components/ui/Header";
import Layout from "../../../../components/ui/Layout";
import { Title } from "../../../../components/ui/styles/Title";
import { getBackendUrl } from "../../../../services/config";
import { deleteCourseFromBackend, getOrgCourses } from "../../../../services/courses";
import { deleteCourseFromBackend, getOrgCourses } from "../../../../services/courses/courses";
import { getOrganizationContextInfo } from "../../../../services/orgs";
const CoursesIndexPage = () => {

View file

@ -1,9 +1,9 @@
import { useRouter } from "next/router";
import React from "react";
import { Header } from "../../../../../components/ui/header";
import Layout from "../../../../../components/ui/layout";
import { Title } from "../../../../../components/ui/styles/title";
import { createNewCourse } from "../../../../../services/courses";
import { Header } from "../../../../../components/ui/Header";
import Layout from "../../../../../components/ui/Layout";
import { Title } from "../../../../../components/ui/styles/Title";
import { createNewCourse } from "../../../../../services/courses/courses";
import { getOrganizationContextInfo } from "../../../../../services/orgs";
const NewCoursePage = () => {

View file

@ -1,8 +1,8 @@
import React from "react";
import { useRouter } from "next/router";
import Layout from "../../../components/ui/layout";
import { Title } from "../../../components/ui/styles/title";
import { Header } from "../../../components/ui/header";
import Layout from "../../../components/ui/Layout";
import { Title } from "../../../components/ui/styles/Title";
import { Header } from "../../../components/ui/Header";
import Link from "next/link";
const OrgHomePage = () => {

View file

@ -1,8 +1,8 @@
import Link from "next/link";
import React from "react";
import AuthenticatedOnly from "../../components/security/AuthenticatedOnly";
import Layout from "../../components/ui/layout";
import { Title } from "../../components/ui/styles/title";
import Layout from "../../components/ui/Layout";
import { Title } from "../../components/ui/styles/Title";
import { deleteOrganizationFromBackend, getUserOrganizations } from "../../services/orgs";
const Organizations = () => {

View file

@ -1,6 +1,6 @@
import React from "react";
import Layout from "../../components/ui/layout";
import { Title } from "../../components/ui/styles/title";
import Layout from "../../components/ui/Layout";
import { Title } from "../../components/ui/styles/Title";
import { createNewOrganization } from "../../services/orgs";
const Organizations = () => {

View file

@ -1,7 +1,7 @@
import React from "react";
import { Header } from "../components/ui/header";
import Layout from "../components/ui/layout";
import { Title } from "../components/ui/styles/title";
import { Header } from "../components/ui/Header";
import Layout from "../components/ui/Layout";
import { Title } from "../components/ui/styles/Title";
import { signup } from "../services/auth/auth";
const SignUp = () => {

View file

@ -1,5 +1,5 @@
import { initialData } from "../components/drags/data";
import { getAPIUrl } from "./config";
import { initialData } from "../../components/drags/data";
import { getAPIUrl } from "../config";
export async function getCourseChaptersMetadata(course_id: any) {
const HeadersConfig = new Headers({ "Content-Type": "application/json" });

View file

@ -1,4 +1,4 @@
import { getAPIUrl } from "./config";
import { getAPIUrl } from "../config";
export async function getOrgCourses(org_id: number) {
const HeadersConfig = new Headers({ "Content-Type": "application/json" });

View file

@ -0,0 +1,27 @@
import { getAPIUrl } from "../config";
export async function createElement(data: any, chapter_id: any) {
data.content = {}
console.log("data", data, chapter_id);
// remove chapter_id from data
delete data.chapterId;
const HeadersConfig = new Headers({ "Content-Type": "application/json" });
const requestOptions: any = {
method: "POST",
headers: HeadersConfig,
redirect: "follow",
credentials: "include",
body: JSON.stringify(data),
};
const result: any = await fetch(`${getAPIUrl()}elements/?coursechapter_id=${chapter_id}`, requestOptions)
.then((result) => result.json())
.catch((error) => console.log("error", error));
console.log("result", result);
return result;
}

View file

@ -4,20 +4,18 @@ from typing import List
from uuid import uuid4
from pydantic import BaseModel
from src.services.courses.courses import Course, CourseInDB
from src.services.courses.elements import Element, ElementInDB
from src.services.database import create_config_collection, check_database, create_database, learnhouseDB, learnhouseDB
from src.services.security import verify_user_rights_with_roles
from src.services.users import PublicUser
from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks, UploadFile, File
class CourseElement(BaseModel):
element_id: str
class CourseChapter(BaseModel):
name: str
description: str
elements: List[CourseElement]
elements: list
class CourseChapterInDB(CourseChapter):
@ -164,13 +162,19 @@ async def get_coursechapters_meta(course_id: str, current_user: PublicUser):
await check_database()
coursechapters = learnhouseDB["coursechapters"]
courses = learnhouseDB["courses"]
elements = learnhouseDB["elements"]
coursechapters = coursechapters.find(
{"course_id": course_id}).sort("name", 1)
course = courses.find_one({"course_id": course_id})
course = Course(**course) # type: ignore
# elements
coursechapter_elementIds_global = []
# chapters
chapters = {}
for coursechapter in coursechapters:
@ -178,15 +182,27 @@ async def get_coursechapters_meta(course_id: str, current_user: PublicUser):
coursechapter_elementIds = []
for element in coursechapter.elements:
coursechapter_elementIds.append(element.element_id)
coursechapter_elementIds.append(element)
coursechapter_elementIds_global.append(element)
chapters[coursechapter.coursechapter_id] = {
"id": coursechapter.coursechapter_id, "name": coursechapter.name, "elementIds": coursechapter_elementIds
}
# elements
elements_list = {}
for element in elements.find({"element_id": {"$in": coursechapter_elementIds_global}}):
element = ElementInDB(**element)
elements_list[element.element_id] = {
"id": element.element_id, "name": element.name, "type": element.type , "content": element.content
}
final = {
"chapters": chapters,
"chapterOrder": course.chapters
"chapterOrder": course.chapters,
"elements" : elements_list
}
return final

View file

@ -8,11 +8,11 @@ from datetime import datetime
#### Classes ####################################################
class Element(BaseModel):
name: str
element_type: str
content: str
type: str
content: object
class ElementInDB(Element):
@ -29,9 +29,10 @@ class ElementInDB(Element):
####################################################
async def create_element(element_object: Element, coursechapter_id : str , current_user: PublicUser):
async def create_element(element_object: Element, coursechapter_id: str, current_user: PublicUser):
await check_database()
elements = learnhouseDB["elements"]
coursechapters = learnhouseDB["coursechapters"]
# generate element_id
element_id = str(f"element_{uuid4()}")
@ -47,6 +48,10 @@ async def create_element(element_object: Element, coursechapter_id : str , curre
datetime.now()), coursechapter_id=coursechapter_id, updateDate=str(datetime.now()), element_id=element_id)
elements.insert_one(element.dict())
# update chapter
coursechapters.update_one({"coursechapter_id": coursechapter_id}, {
"$addToSet": {"elements": element_id}})
return element
@ -88,7 +93,7 @@ async def update_element(element_object: Element, element_id: str, current_user:
datetime_object = datetime.now()
updated_course = ElementInDB(
element_id=element_id,coursechapter_id=element["coursechapter_id"] ,creationDate=creationDate, updateDate=str(datetime_object), **element_object.dict())
element_id=element_id, coursechapter_id=element["coursechapter_id"], creationDate=creationDate, updateDate=str(datetime_object), **element_object.dict())
elements.update_one({"element_id": element_id}, {
"$set": updated_course.dict()})

View file

@ -26,6 +26,7 @@ class Elements(BaseModel):
collections: List[str]
organizations: List[str]
coursechapters: List[str]
elements : List[str]
class Role(BaseModel):